Unable to create converter for class when using sealed class or an interface with Moshi

前端 未结 1 406
长情又很酷
长情又很酷 2021-01-16 01:29

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

1条回答
  •  小鲜肉
    小鲜肉 (楼主)
    2021-01-16 01:42

    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
    }
    

    0 讨论(0)
提交回复
热议问题