Question is similar to this but different in that the type
is not contained within the JSON object being deserialized but is instead contained in the outer level (root).
Like so:
{
"type": "A",
"myObject" : { <- type is NOT stored in here
...
}
}
The intention is to map this JSON object to Blah
// Code is in Kotlin
interface Foo {
...
}
class Bar : Foo { // Map to this if 'type' is A
...
}
class Baz : Foo { // Map to this if 'type' is B
...
}
class Blah {
val type : String? = null
val myObject : Foo? = null
}
How do I make myObject
map to Bar
if type
is A
and Baz
if type
is B
?
Temporarily, I've resorted to reading in the root JSON object manually. Any help would be much appreciated. Thanks.
EDIT:
When trying to map the root JSON object to Blah
using Gson
fromJson
method, I get this error: Unable to invoke no-args constructor for class Foo
. - But this is incorrect anyway, because I need myObject
to map specifically to either Baz
or Bar
.
Here is my solution to this. Solution is in Kotlin.
Edit class Blah
:
class Blah {
val type : String? = null
@ExcludeOnDeserialization // <- add this
val myObject : Foo? = null
}
Inside some static class:
inline fun <T : Annotation>findAnnotatedFields(
annotation: Class<T>,
clazz : Class<*>,
onFind : (Field) -> Unit
){
for(field in clazz.declaredFields){
if(field.getAnnotation(annotation)!=null){
field.isAccessible = true
onFind(field)
}
}
}
Create new class and annotation:
@Target(AnnotationTarget.FIELD)
annotation class ExcludeOnDeserialization
class GsonExclusionStrategy : ExclusionStrategy {
override fun shouldSkipClass(clazz: Class<*>?): Boolean {
return clazz?.getAnnotation(ExcludeOnDeserialization::class.java) != null
}
override fun shouldSkipField(f: FieldAttributes?): Boolean {
return f?.getAnnotation(ExcludeOnDeserialization::class.java) != null
}
}
Read root json object:
...
val gson = GsonBuilder()
.addDeserializationExclusionStrategy(GsonExclusionStrategy())
.create()
val rootJsonObject = JsonParser().parse(rootJsonObjectAsString)
val blah = gson.fromJson(rootJsonObject, Blah::class.java)
findAnnotatedFields(
ExcludeOnDeserialization::class.java,
Blah::class.java
){ foundExcludedField -> // foundExcludedField = 'myObject' declared in 'Blah' class
val myObjectAsJsonObject
= rootJsonObject.asJsonObject.getAsJsonObject(foundExcludedField.name)
when (foundExcludedField.type) {
Foo::class.java -> {
when (blah.type) {
"A" -> {
foundExcludedField.set(
blah,
gson.fromJson(myObjectAsJsonObject, Bar::class.java)
)
}
"B" -> {
foundExcludedField.set(
blah,
gson.fromJson(myObjectAsJsonObject, Baz::class.java)
)
}
else -> return null
}
}
}
}
// The root json object has now fully been mapped into 'blah'
Inspiration for this solution came from this article