11

In an Android App I need to deserialize Json data for Kotlin data classes, with a single abstraction level. But I don't have any idea, to place correct properties in constructors.

As a simple version, let's say I have a Shape:

abstract class Shape(open val x: Int, open val y: Int)

with two derivations

data class Circle(val radius: Int, override val x: Int, override val y: Int): Shape(x, y)

and

data class Square(val width: Int, override val x: Int, override val y: Int): Shape(x, y)

So my target is, not to instantiate a Shape. So, instead always deserialize its derivations. Later I need to handle some collection properties in other classes like:

val shapes: List<Shape>

but I also have to know the derived type of each element.

When I try to deserialize the given example with Gson

val square = gson.fromJson(SQUARE_JSON, Square::class.java)

I always get an IllegalArgumentException

java.lang.IllegalArgumentException: class com.example.shapes.model.Square declares multiple JSON fields named x

at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:170)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:100)
at com.google.gson.Gson.getAdapter(Gson.java:423)
at com.google.gson.Gson.fromJson(Gson.java:886)
at com.google.gson.Gson.fromJson(Gson.java:852)
at com.google.gson.Gson.fromJson(Gson.java:801)
at com.google.gson.Gson.fromJson(Gson.java:773)

Is there any possibility to deserialize this classes correctly without writing a custom Gson TypeAdapter or loosing data class advantages?

Thanks

elcolto
  • 961
  • 7
  • 11

3 Answers3

17

Both Shape and Square declare fields x and y. An instance of Square actually stores the value for x and y twice. To avoid this you can remove x and y from the Shape constructor and declare them as abstract. The same goes for Circle e.g.:

abstract class Shape {
    abstract val x: Int
    abstract val y: Int
}

data class Circle(val radius: Int, override val x: Int, override val y: Int) : Shape()

data class Square(val width: Int, override val x: Int, override val y: Int) : Shape()

Gson will now be able to successfully serialize and deserialize Circle and Square objects.

mfulton26
  • 29,956
  • 6
  • 64
  • 88
4

If you're not able to convert the base class to Kotlin just yet, the @Transient annotation worked for me.

data class Circle(val radius: Int, 
    @Transient val x: Int, 
    @Transient val y: Int): Shape(x, y)

data class Square(val width: Int, 
    @Transient val x: Int, 
    @Transient val y: Int): Shape(x, y)
loeschg
  • 29,961
  • 26
  • 97
  • 150
1

Are you sure it is Gson problem with data classes? It should work with them correctly, only non nullable fields may be a problem.

Your error indicates that you have fields with same name in it.
If there's any particular field you don't want to include you have to annotate it with @Transient

John
  • 1,304
  • 1
  • 9
  • 17