Reputation: 728
We try to deserialize JSON field from 0, 1, null to "Boolean?" And we have this customize Annotation:
@Retention(AnnotationRetention.RUNTIME)
@JsonQualifier
annotation class BooleanType
class BooleanAdapter {
@FromJson
@BooleanType
fun fromJson(value: Int): Boolean {
return value == 1
}
@ToJson
fun toJson(@BooleanType value: Boolean): Int {
return if (value) 1 else 0
}
}
Everything works fine if the field is 0 or 1. But when it comes to "null", it always throw exception
No JsonAdapter for class java.lang.Boolean annotated [@com.pk.data.serialize.BooleanType()]
....
Here is the dummy data and unit test
@JsonClass(generateAdapter = true)
data class HaHaData(
@BooleanType
@Json(name = "haha") val haha: Boolean?,
)
@Test
fun parseNullBooleanTest() {
val moshi = Moshi.Builder()
.add(BooleanAdapter())
.build()
val json = "{\"haha\":1}"
val jsonWithNull = "{\"haha\":null}"
val data = HaHaDataJsonAdapter(moshi).fromJson(json)
val dataWithNull = HaHaDataJsonAdapter(moshi).fromJson(jsonWithNull) // exception thrown
assert(data?.haha== true)
assert(dataWithNull?.haha == null)
}
Upvotes: 1
Views: 838
Reputation: 548
The solution to the problem has already been provided by @AlexT. The input types in the adapter have to be nullable and if the output type is nullable (as it is in the original question), it also has to be nullable in the Adapter.
So a working adapter could look like this:
@Retention(AnnotationRetention.RUNTIME)
@JsonQualifier
annotation class BooleanType
class BooleanAdapter {
@FromJson
@BooleanType
fun fromJson(value: Int?): Boolean? {
return when (value) {
null -> null
1 -> true
else -> false
}
}
@ToJson
fun toJson(@BooleanType value: Boolean?): Int? {
return when (value) {
null -> null
true -> 1
false -> 0
}
}
}
But for all those people who need an Adapter that can accept both an Int and a Boolean (and a String) at the same time, here's the solution for that:
@Retention(AnnotationRetention.RUNTIME)
@JsonQualifier
annotation class BooleanType
class BooleanAdapter {
@FromJson
@BooleanType
fun fromJson(reader: JsonReader): Boolean? {
return when (reader.peek()) {
JsonReader.Token.NULL -> reader.nextNull()
JsonReader.Token.BOOLEAN -> reader.nextBoolean()
JsonReader.Token.NUMBER -> when (val value = reader.nextInt() {
0 -> false
1 -> true
else -> throw JsonDataException("Number $value can not be transformed to a Boolean.")
}
JsonReader.Token.STRING -> when (val value = reader.nextString()) {
"false".equals(value, ignoreCase = true) -> false
"true".equals(value, ignoreCase = true) -> true
"0" == value -> false
"1" == value -> true
else -> throw JsonDataException("String $value can not be transformed to a Boolean.")
}
else -> throw JsonDataException("Unknown value can not be transformed to a Boolean")
}
}
@ToJson
fun toJson(@BooleanType value: Boolean?): Boolean? {
// This adapter is not intended to be used for serializing, as it is impossible for it to know what the output type should be.
return value
}
}
or if you prefer to use Any?
as an input type instead of JsonReader
:
@Retention(AnnotationRetention.RUNTIME)
@JsonQualifier
annotation class BooleanType
class BooleanAdapter {
@FromJson
@BooleanType
fun fromJson(value: Any?): Boolean? {
return when (value) {
null -> null
is Boolean -> value
is Number -> when (value.toInt()) {
0 -> false
1 -> true
else -> throw JsonDataException("Number $value can not be transformed to a Boolean.")
}
is String -> when {
"true".equals(value, ignoreCase = true) -> true
"false".equals(value, ignoreCase = true) -> false
"1" == value -> true
"0" == value -> false
else -> throw JsonDataException("String $value can not be transformed to a Boolean.")
}
else -> throw JsonDataException("Unknown value $value can not be transformed to a Boolean.")
}
}
@ToJson
fun toJson(@BooleanType value: Boolean?): Boolean? {
// This adapter is not intended to be used for serializing, as it is impossible for it to know what the correct output type should be.
return value
}
}
Upvotes: 1
Reputation: 2984
Your fromJson
does not accept null
as an input.
If you want null
to become false:
fun fromJson(value: Int?): Boolean {
return value == 1
}
If you want null
to stay null
(you'll also have to modify the toJson
fun to accept nulls as well then):
fun fromJson(value: Int?): Boolean? {
return value?.let { it == 1 }
}
Upvotes: 2