How to use jackson to deserialize to Kotlin collections

前端 未结 3 869
长情又很酷
长情又很酷 2020-12-01 17:53

Sample code what I want:

data class D(val a: String, val b: Int)
val jsonStr = \"\"\"[{\"a\": \"value1\", \"b\": 1}, {\"a\": \"value2\", \"b\":\"2}]\"\"\"
//         


        
相关标签:
3条回答
  • 2020-12-01 18:30

    With Jackson Kotlin Module current versions, if you import the full module package or the specific extension function you'll have all extension methods available. Such as:

    import com.fasterxml.jackson.module.kotlin.*  
    val JSON = jacksonObjectMapper()  // keep around and re-use
    val myList: List<String> = JSON.readValue("""["a","b","c"]""")
    

    Therefore the Jackson Module for Kotlin will infer the the correct type and you do not need a TypeReference instance.

    so your case (slightly renamed and fixed the data class, and JSON):

    import com.fasterxml.jackson.module.kotlin.readValue
    
    data class MyData(val a: String, val b: Int)
    val JSON = jacksonObjectMapper()  
    
    val jsonStr = """[{"a": "value1", "b": 1}, {"a": "value2", "b": 2}]"""
    val myList: List<MyData> = JSON.readValue(jsonStr)
    

    You can also use the form:

    val myList = JSON.readValue<List<MyData>>(jsonStr)
    

    Without the import you will have an error because the extension function is not found.

    0 讨论(0)
  • 2020-12-01 18:37

    TL;DR

    With Jackson 2.6.3-2 do as @jason-minard advises and simply use:

    import com.fasterxml.jackson.module.kotlin.readValue
    
    val listOfD: List<D> = jacksonMapper.readValue(jsonStr)
    

    Details

    There's nothing special about Kotlin wrt to deserializing collections, although you'll need the kotlin jackson module to deserialize data classes without annotations.

    Namely, it will require full reified type information in your case in order to keep track of the generic parameter of the list (D) ; otherwise (e.g. if you use readValue(jsonStr, List::class.java)) Jackson will only see it as an erased type (i.e. List) (as Kotlin makes explicit) and deserialize it to a List<Map<String, String>>, not knowing it has to construct Ds. This is worked around by using an anonymous subclass of TypeReference in Java so that Jackson can access the full reified type to deserialize to at run time.

    Translating Jackson Java code literally to Kotlin, the following thus achieves what you want (and as commented by @eski, notice that the JSON in your example is invalid):

    val jacksonMapper = ObjectMapper().registerModule(KotlinModule())
    
    val jsonStr = """[{"a": "value1", "b": 1}, {"a": "value2", "b":2}]"""
    val listOfD: List<D> = jacksonMapper.readValue(jsonStr, object : TypeReference<List<D>>(){})
    
    assertEquals(listOf(D("value1", 1), D("value2", 2)), listOfD)
    

    This is a bit verbose and ugly though, so you could hide it in a Kotlin (extension) function (especially if you plan on using it several times):

    inline fun <reified T> ObjectMapper.readValue(json: String): T = readValue(json, object : TypeReference<T>(){})
    

    which would allow you to then just call:

    val listOfD: List<D> = jacksonMapper.readValue(jsonStr)
    

    And this is just what's included in the 2.6.3-2 jackson Kotlin module.

    0 讨论(0)
  • 2020-12-01 18:49

    If you don't have the type of your generic content, like <MyData> in the example below:

    val value = context.readValue<List<MyData>>(parser, valueType)
    

    You can make it by implementing not only the JsonSerialzier but also the ContextualDeserializer like in the code sample below (based on this answer in Java):

    class GenericListDeserializer : JsonDeserializer<List<*>>(), ContextualDeserializer {
        private var valueType: JavaType? = null
    
        override fun createContextual(ctxt: DeserializationContext, property: BeanProperty): JsonDeserializer<List<*>> {
            val wrapperType = property.type
            val valueType = wrapperType.containedType(0)
            val deserializer = GenericListDeserializer()
            deserializer.valueType = valueType
            return deserializer
        }
    
        override fun deserialize(parser: JsonParser, context: DeserializationContext): List<*> {
            val value :Any? = context.readValue(parser, valueType)
            return listOf(value)
        }
    }
    
    0 讨论(0)
提交回复
热议问题