I am trying to parse a json data from a server.It has dynamic keys so I am trying to have like a parent class that have the shared keys and child class for each specific nod
You can make use of JsonAdapter from moshi to parse different JSON Models if you can differentiate them by foreseeing some value in the json.
for example, consider json response having two schemas,
{
"root": {
"subroot": {
"prop" : "hello",
"type" : "String"
}
}
}
(or)
{
"root": {
"subroot": {
"prop" : 100,
"type" : "Integer"
}
}
}
Here, subroot has different schemas (one containing string property and another containg a integer property) which can be identified by "type"
You can create a parent sealed class with common keys and derive few child classes with varying keys. Write a adapter to select the type of class to be used while json serialization and add that adapter to moshi builder.
Model classes:
class Response {
@Json(name = "root")
val root: Root? = null
}
class Root {
@Json(name = "subroot")
val subroot: HybridModel? = null
}
sealed class HybridModel {
@Json(name = "type")
val type: String? = null
class StringModel : HybridModel() {
@Json(name = "prop")
val prop: String? = null
}
class IntegerModel : HybridModel() {
@Json(name = "prop")
val prop: Int? = null
}
}
Few extension methods to JsonReader,
inline fun JsonReader.readObject(process: () -> Unit) {
beginObject()
while (hasNext()) {
process()
}
endObject()
}
fun JsonReader.skipNameAndValue() {
skipName()
skipValue()
}
HybridAdapter to select type of class for "subroot" key
class HybridAdapter : JsonAdapter() {
@FromJson
override fun fromJson(reader: JsonReader): HybridModel {
var type: String = ""
// copy reader and foresee type
val copy = reader.peekJson()
copy.readObject {
when (copy.selectName(JsonReader.Options.of("type"))) {
0 -> {
type = copy.nextString()
}
else -> copy.skipNameAndValue()
}
}
//handle exception if type cannot be identified
if (type.isEmpty()) throw JsonDataException("missing type")
// build model based on type
val moshi = Moshi.Builder().build()
return if (type == "String")
moshi.adapter(HybridModel.StringModel::class.java).fromJson(reader)!!
else
moshi.adapter(HybridModel.IntegerModel::class.java).fromJson(reader)!!
}
@ToJson
override fun toJson(p0: JsonWriter, p1: HybridModel?) {
// serialization logic
}
}
Finally build Moshi with the HybridAdapter to serialize HybridModel,
fun printProp(response: Response?) {
val subroot = response?.root?.subroot
when (subroot) {
is HybridModel.StringModel -> println("string model: ${subroot.prop}")
is HybridModel.IntegerModel -> println("Integer model: ${subroot.prop}")
}
}
fun main() {
val jsonWithStringSubroot =
"""
{
"root": {
"subroot": {
"prop" : "hello",
"type" : "String"
}
}
}
"""
val jsonWithIntegerSubroot =
"""
{
"root": {
"subroot": {
"prop" : 1,
"type" : "Integer"
}
}
}
"""
val moshi = Moshi.Builder().add(HybridAdapter()).build()
val response1 = moshi.adapter(Response::class.java).fromJson(jsonWithStringSubroot)
printProp(response1) // contains HybridModel.StringModel
val response2 = moshi.adapter(Response::class.java).fromJson(jsonWithIntegerSubroot)
printProp(response2) // contains HybridModel.IntegerModel
}