How do I deserialize JSON into a List wi

2019-02-16 12:34发布

问题:

This question already has an answer here:

  • How to use jackson to deserialize to Kotlin collections 3 answers

What is the correct syntax to deserialize the following JSON:

[ {
  "id" : "1",
  "name" : "Blues"
}, {
  "id" : "0",
  "name" : "Rock"
} ]

I tried:

//Works OK
val dtos  = mapper.readValue(json, List::class.java)

However I want:

val dtos : List<GenreDTO>  = mapper.readValue(json, 
    List<GenreDTO>::class.java)

The above syntax is not correct and gives: only classes are allowed on the left hand side of a class literal

回答1:

NOTE: The answer from @IRus is also correct, it was being modified at the same time I wrote this to fill in more details.

You should use the Jackson + Kotlin module or you will have other problems deserializing into Kotlin objects when you do no have a default constructor.

Your first sample of the code:

val dtos  = mapper.readValue(json, List::class.java)

Is returning an inferred type of List<*> since you did not specify more type information, and it is actually a List<Map<String,Any>> which is not really "working OK" but is not producing any errors. It is unsafe, not typed.

The second code should be:

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue

val mapper = jacksonObjectMapper()
// ...
val genres: List<GenreDTO> = mapper.readValue(json)

You do not need anything else on the right side of the assignment, the Kotlin module for Jackson will reify the generics and create the TypeReference for Jackson internally. Notice the readValue import, you need that or .* for the com.fasterxml.jackson.module.kotlin package to have the extension functions that do all of the magic.

A slightly different alternative that also works:

val genres = mapper.readValue<List<GenreDTO>>(json)

There is no reason to NOT use the extension functions and the add-on module for Jackson. It is small and solves other issues that would require you to jump through hoops to make a default constructor, or use a bunch of annotations. With the module, your class can be normal Kotlin (optional to be data class):

class GenreDTO(val id: Int, val name: String)


回答2:

Following code works well for me:

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import com.fasterxml.jackson.module.kotlin.registerKotlinModule



val json = """[ {
  "id" : "1",
  "name" : "Blues"
}, {
  "id" : "0",
  "name" : "Rock"
} ]"""

data class GenreDTO(val id: Int, val name: String)

val mapper = ObjectMapper().registerKotlinModule()

fun main(args: Array<String>) {
    val obj: List<GenreDTO> = mapper.readValue(json)
    obj.forEach {
        println(it)
    }
}

This work because of extension function defined inside jackson-kotlin-module (that used reified generics):

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

Thanks @JaysonMinard for notify me about it.

Output:

GenreDTO(id=1, name=Blues)
GenreDTO(id=0, name=Rock)


回答3:

The error you're getting is about following expression:

List<GenreDTO>::class.java

Because of how jvm treats generics there's no separate class for List<GenreDTO> thus compiler complains. Similarly in Java the following will not compile:

List<GenreDTO>.getClass()

Here's a sample that will deserialize the list properly:

val value:List<GenreDTO> = mapper.readValue(json, object : TypeReference<List<GenreDTO>>() {})

As @JaysonMinard has pointed out you can use jackson-module-kotlin to simplify the invocation to:

val genres: List<GenreDTO> = mapper.readValue(json)
// or
val genres = mapper.readValue<List<GenreDTO>>(json)

This is possible because of reified type parameters. Consider looking at Extensions to find out details.