Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom boolean deserializer not working in Gson

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?

like image 617
artem Avatar asked May 15 '18 22:05

artem


2 Answers

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.

like image 176
tynn Avatar answered Nov 12 '22 00:11

tynn


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.

like image 1
Samuel Avatar answered Nov 11 '22 22:11

Samuel