sealed class Shape
class Circle(val radius: Double) : Shape
class Rectangle(val width: Double) : Shape
There are 2 types of polymorphism:
[ { "type": "CIRCLE", "radius": 10.0 }, { "type": "RECTANGLE", "width": 20.0 } ]
1.2 Discriminator external
[ { "type": "CIRCLE", "data": { "radius": 10.0 } }, { "type": "RECTANGLE", "data": { "width": 20.0 } } ]
anyOf from Swagger[ { "radius": 10.0 }, { "width": 20.0 } ]
Because only Circle class has radius field, first object from list will be deserialized in Circle class.
Also should be possible to do nested deserialization(multi level).
Please add support for all this cases.
Moshi can do all of these, the PolymorphicJsonAdapterFactory as a first-party helper only supports the first type. Is your proposal that it also support these other two variants in PolymorphicJsonAdapterFactory?
Yes. Will be great to have built-in solutions for these most popular approaches.
I like 1.1 best and that's what we include in the box. The other variants are all possible but we're only including one kind by default to reduce the number of inconsequential decisions developers have to make.
For 1.2 - you can make "data" the sealed class and then make the enclosing type a holder type
data class ShapeObject(val type: ShapeType, data: Shape)
sealed class Shape
class Circle(val radius: Double) : Shape
class Rectangle(val width: Double) : Shape
enum class ShapeType(val derivedClass: Class<out Shape>) {
CIRCLE(Circle::class.java),
RECTANGLE(Rectangle::class.java)
}
Then write a delegating adapter + factory
class ShapeObjectFactory : JsonAdapter.Factory {
override fun create(
type: Type, annotations: Set<Annotation>, moshi: Moshi): JsonAdapter<ShapeObject>? {
if (!Types.getRawType(type).isAssignableFrom(ShapeObject::class.java)) {
return null
}
return object : JsonAdapter<ShapeObject>() {
private val shapeTypeAdapter = moshi.adapter<ShapeType>(ShapeType::class.java)
override fun fromJson(reader: JsonReader): ShapeObject? {
val jsonValue = reader.readJsonValue()
@Suppress("UNCHECKED_CAST")
val value = jsonValue as Map<String, Any>
val shapeType = shapeTypeAdapter.fromJsonValue(value["type"])
val shape = moshi.adapter(shapeType.derivedClass).fromJsonValue(value["data"]) ?: throw JsonDataException()
return ShapeObject(shapeType, shape)
}
override fun toJson(writer: JsonWriter, value: ShapeObject?) {
writer.beginObject()
writer.name("type")
shapeTypeAdapter.toJson(writer, value.type)
writer.name("data")
moshi.adapter(value.type.derivedClass).toJson(writer, value.data)
writer.endObject()
}
}
}
}
For #2 - I'm not sure how you could really do that without some sort of sentinel for the type without just trying them until finding one that works. That said, if you want to do that, this blog post covers that a bit: http://blog.nightlynexus.com/peeking-streams-with-moshi/. Under the "Guarding against unexpected JSON formats." section, you would basically have to try them until it works, but it would be horrible for performance
For 2 point, we need first to determinate all the children of a parent class, then for every child class should be determinate what fields are unique(and required) per class. So on deserialization should be checked if an object contains that unique fields. You can see that as 1.1 but with multiple discriminators.
No action to take on this.
The proposed solution seems convoluted. Why not simply do this:
sealed class Shape
class Circle(val radius: Double) : Shape
class Rectangle(val width: Double) : Shape
@Suppress("UNCHECKED_CAST")
class ShapeAdapter {
@FromJson
fun parse(reader: JsonReader,
circleAdapter: JsonAdapter<Circle>,
rectAdapter: JsonAdapter<Rectangle>): Shape {
val jsonObj = reader.readJsonValue() as Map<String, Any>
val adapter = when (val type = jsonObj["type"]) {
"circle" -> circleAdapter
"rectangle" -> rectAdapter
else -> throw IllegalStateException("unexpected type: $type")
}
return adapter.fromJsonValue(jsonObj)!!
}
...
}
Most helpful comment
Yes. Will be great to have built-in solutions for these most popular approaches.