6

I have to deserialize 0 to false and 1 to true.

I've created this class:

class IntBooleanDeserializer : JsonDeserializer<Boolean?> {
    override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Boolean? {
        json?.let {
            return json.asInt == 1
        }

        return null
    }
}

And registered it:

private val gson = GsonBuilder()
            .registerTypeAdapter(Boolean::class.java, IntBooleanDeserializer())
            .create()

And created test class for this:

data class BooleanClass(val value: Boolean?)

And then:

gson.fromJson("{\"value\": 0}", BooleanClass::class.java)

This code throws exception:

com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected a boolean but was NUMBER at line 1 column 12 path $.value

Seems that Gson does not use my deserializer for Boolean?, but successfully uses other custom deserializers for other types (for example, for enums).

Why?

artem
  • 16,382
  • 34
  • 113
  • 189
  • 1
    Is `Boolean::class.java` the same as `Boolean?::class.java` (that is non-null vs. nullable) or is the latter undefined in Kotlin? I must admit my knowledge of Kotlin is patchy, but you register a deserialiser for `Boolean` and your data structure type is `Boolean?` – Oleg Sklyar May 15 '18 at 22:20

2 Answers2

9

You're registering the deserializer for Boolean::class.java which is boolean while the type you require actually is Boolean? translating to java.lang.Boolean.

To get java.lang.Boolean you have to use Boolean::class.javaObjectType for the registration.

You'd also find the same behavior for all primitive Java types.

tynn
  • 38,113
  • 8
  • 108
  • 143
1

Inspired by @tynn's answer and this post. I design two TypeAdaters to cover Boolean and Boolean? type in Kotlin.

private val TRUE_STRINGS: Array<String> = arrayOf("true", "1")

class BooleanObjectTypeAdapter : JsonDeserializer<Boolean?>, JsonSerializer<Boolean?> {

    override fun serialize(src: Boolean?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
        return if (src == null) {
            JsonNull.INSTANCE
       } else {
           JsonPrimitive(src)
       }
    }

    override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Boolean? {
        if (json == null || json.isJsonNull) {
            return null
        }

        return when {
            json !is JsonPrimitive -> false
            json.isBoolean -> json.asBoolean
            json.isNumber -> json.asNumber.toInt() == 1
            json.isString -> TRUE_STRINGS.contains(json.asString.lowercase())
            else -> false
        }
    }
}

class BooleanPrimitiveTypeAdapter : JsonDeserializer<Boolean>, JsonSerializer<Boolean> {

    override fun serialize(src: Boolean?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
        return if (src == null) {
            JsonPrimitive(false)
        } else {
            JsonPrimitive(src)
        }
    }

    override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Boolean {
        if (json == null || json.isJsonNull) {
            return false
        }

        return when {
            json !is JsonPrimitive -> false
            json.isBoolean -> json.asBoolean
            json.isNumber -> json.asNumber.toInt() == 1
            json.isString -> TRUE_STRINGS.contains(json.asString.lowercase())
            else -> false
        }
    }
}

You can register these adapters by

GsonBuilder()
    .registerTypeAdapter(Boolean::class.javaObjectType, BooleanObjectTypeAdapter())
    .registerTypeAdapter(Boolean::class.javaPrimitiveType, BooleanPrimitiveTypeAdapter())
    .create()

I also create a gist to keep these and write tests.

Samuel
  • 201
  • 2
  • 5