Как десериализовать JSON в список с помощью Kotlin + Jackson [дубликат]

этот вопрос уже есть ответ здесь:

каков правильный синтаксис для десериализации следующего JSON:

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

пробовал:

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

однако я хочу:

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

выше синтаксис не правильно и дает:only classes are allowed on the left hand side of a class literal

3 ответов


Примечание: ответ от @IRus также правильный, он был изменен в то же время, когда я написал это, чтобы заполнить более подробную информацию.

вы должны использовать модуль Джексона + Котлина или у вас будут другие проблемы десериализации в объекты Kotlin, когда у вас нет конструктора по умолчанию.

ваш первый пример кода:

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

возвращает предполагаемый тип List<*> поскольку вы не указали больше информации о типе, и это на самом деле List<Map<String,Any>> который на самом деле не "работает нормально", но не вызывает ошибок. Это небезопасно,не набрано.

второй код должен быть:

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

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

вам больше ничего не нужно в правой части задания, модуль Котлина для Джексона будет олицетворять дженерики и создавать TypeReference для Джексона внутренне. Обратите внимание на readValue импорт, вам это нужно или .* на com.fasterxml.jackson.module.kotlin пакета имейте функции расширения, которые делают всю магию.

немного другая альтернатива, которая также работает:

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

нет причин не использовать функции расширения и дополнительный модуль для Jackson. Он небольшой и решает другие проблемы, которые потребуют от вас прыгать через обручи, чтобы сделать конструктор по умолчанию или использовать кучу аннотаций. С модулем ваш класс может быть обычным Kotlin (необязательно быть data класс):

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

следующий код хорошо работает для меня:

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)
    }
}

эта работа из-за функции расширения, определенной внутри jackson-kotlin-module (который использовал овеществленные генерики):

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

спасибо @JaysonMinard за уведомление меня об этом.

выход:

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

ошибка, которую вы получаете, заключается в следующем выражении:

List<GenreDTO>::class.java

из-за того, как jvm обрабатывает дженерики, нет отдельного класса для List<GenreDTO> таким образом, компилятор жалуется. Аналогично в Java следующее не будет компилироваться:

List<GenreDTO>.getClass()

вот пример, который десериализует список правильно:

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

как отметил @JaysonMinard, вы можете использовать Джексон-модуль-Котлин для упрощения вызова кому:

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

это возможно из-за reified type parameters. Рассмотрим глядя на Extensions чтобы узнать подробности.