Kotlin JSONObject$Null Bug


#1

Hey,

Im new here, so sorry if i posted this in the wrong section!

I just found this bug for Kotlin while using the org.json library.
When JSON returns null, it gives a JSONObject$Null
While checking for null, == fails, and .equals warns to use ==

My code:

fun testNullJSON(jsonObject: JSONObject, valueKey: String){
val jsonArray = jsonObject.getJSONArray(valueKey)

for(i in 0 until jsonArray.length()) {
    val jsonArrayValue = jsonArray[i] ?: continue // NOTE: First null check here

    when (jsonArrayValue) {
        is Int -> {
            // My implementation ...
        }

        is String -> {
            // My implementation ...
        }

        is JSONObject -> {
            // My implementation ...
        }

        is JSONArray -> {
            // My implementation ...
        }

        else -> {

            // WARNING: IS ALWAYS FALSE
            if (jsonArrayValue == null) {
                println("jsonArrayValue == null")
            } else {
                println("jsonArrayValue != null")
                println(jsonArrayValue)                // PRINTS: null
            }

            // NOTE: - Checking with Binary results in wrong result!
            @Suppress("ReplaceCallWithBinaryOperator")
            if (jsonArrayValue.equals(null)) {
                println("is a null value") // THIS GET ALSO PRINTED
            } else {
                println("Is not a null value")
                println(jsonArrayValue) //Printed null while using ==
            }
        }
    }
}

}

Result == and .equals null dont give the same result


#2

Uh, how ugly, creating a fake null object. Not really surprising others have a problem with it. Here is a non library specific example:

val fakeNULL: Any = object : Any() {
    override fun equals(o: Any?): Boolean {
        return o === this || o === null // API specifies this broken equals implementation
    }

    override fun toString(): String {
        return "null"
    }
}

fun main(args: Array<String>) {
    val l = listOf( "txt", fakeNULL )
    for(i in 0 until l.count()){
        val v = l[i] ?: continue // elivs operator only cares about real nulls
        
        println(v)

        if( v.equals(null)){ // calls the fake equals implementation
            println("eq null")
        }else{
            println("neq null")
        }

        if (v == null) { // is simplified to v === null
            println("is null")
        } else {
            println("not null")
        }

    	println()
    }
}

I guess the cause for v.equals(null) not being the same as v == null is an optimization:

a == null will be automatically translated to a === null.


#3

I seem to remember that there is an issue about this behavior, but I can’t find it.


#4

This is probably the issue, and the NULL value I had to deal with was a JSONObject$NULL so not a null specific.
But why is the IDE giving me a ReplaceCallWithBinaryOperator warning when its using another implementation.

Sidenote: I already fixed my problem using extra checks, but I wanted to mention this issue in case of anyone needed it!


#5

I was talking about an issue on https://youtrack.jetbrains.com/issues/KT