35

Sample code what I want:

data class D(val a: String, val b: Int)
val jsonStr = """[{"a": "value1", "b": 1}, {"a": "value2", "b":"2}]"""
// what I need
val listOfD: List<D> = jacksonObjectMapper().whatMethodAndParameter?
Marvin
  • 1,726
  • 1
  • 17
  • 25
  • Follow the directions here https://github.com/FasterXML/jackson-module-kotlin and make sure you are parsing valid json (the json you have in your question is not). – eski Oct 27 '15 at 16:05
  • The data class D is also missing "val" for each of the members, therefore will not work either. In Kotlin 1.0.0-beta-1038 and newer that would be a compiler error, before it would be accepted as constructor parameters. – Jayson Minard Oct 28 '15 at 22:36

4 Answers4

64

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.

Jayson Minard
  • 84,842
  • 38
  • 184
  • 227
  • 2
    note that it should be enough to simply import the given extension function in this case, not the whole package: `import com.fasterxml.jackson.module.kotlin.readValue`. – desseim Oct 29 '15 at 14:31
  • See Github for latest version numbers: https://github.com/FasterXML/jackson-module-kotlin#status – Jayson Minard Feb 21 '16 at 17:40
25

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.

Community
  • 1
  • 1
desseim
  • 7,968
  • 2
  • 24
  • 26
1

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)
    }
}
fabriciorissetto
  • 9,475
  • 5
  • 65
  • 73
0

I can do this,

val myList: List<String> = JSON.readValue(jsonString, Array<ObservingLocation>::class.java).toList()
helvete
  • 2,455
  • 13
  • 33
  • 37