Предположим, я получил ошибку от сервера как JSON, который может быть в двух разных структурах:

{"errorMessage": "This action is unauthorized."}

ИЛИ

{"errorMessage":{"changePassword":"Old password is incorrect."}}

Как я могу десериализовать этот тип JSON?

Что я пробовал

Я пытался иметь абстрактный класс "Ошибка" и двух дочерних элементов:

abstract class Error() {}

data class SingleError(val errorMessage: String) : Error() 

data class MultiError(val errorMessage: Map<String, String>) : Error() 

Тогда я пробую:

jacksonObjectMapper().readValue<Error>(response.body)

Десериализовать, но у меня есть исключение:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.fifth_llc.siply.main.request.Forbidden$Error` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
     at [Source: (String)"{"errorMessage":{"changePassword":"Old password is incorrect."}}"; line: 1, column: 1]

Также я пробовал JsonDeserialize annotaiton, но, кажется, я могу использовать его, если я хочу проанализировать конкретный тип:

@JsonDeserialize(`as` = MultiError::class)

Любая помощь?

0
Tigran Babajanyan 21 Авг 2018 в 10:31

3 ответа

Лучший ответ

Индивидуальный подход десериализатора:

sealed class Error() {
    data class SingleError(val errorMessage: String) : Error()
    data class MultiError(val errorMessage: Map<String, String>) : Error()
}
...
class ErrorDeserializer : StdDeserializer<Error>(Error::class.java) {

    companion object {
        private val MAP_TYPE_REFERENCE = object : TypeReference<Map<String, String>>() {}
    }

    @Throws(IOException::class, JsonProcessingException::class)
    override fun deserialize(jp: JsonParser, ctxt: DeserializationContext): Error {
        val mapper = jp.codec as ObjectMapper
        val node: JsonNode = mapper.readTree(jp)
        val msgNode = node.get("errorMessage")
        if (msgNode.isValueNode) {
            val errorMsg = msgNode.asText()
            return Error.SingleError(errorMsg)
        } else {
            val errorMsgs = mapper.readValue<Map<String, String>>(msgNode.toString(), 
                    MAP_TYPE_REFERENCE)
            return Error.MultiError(errorMsgs)
        }
    }
}

Применение:

val mapper = ObjectMapper()
val module = SimpleModule().addDeserializer(Error::class.java, ErrorDeserializer())
mapper.registerModule(module)

val error = mapper.readValue<Error>("json content", Error::class.java)
when (error) {
    is Error.SingleError -> {
        // error.errorMessage
    }
    is Error.MultiError -> {
        // error.errorMessage
    }
}
2
Akaki Kapanadze 21 Авг 2018 в 09:15

Насколько я знаю, по крайней мере, я знал, что вы не можете назначать абстрактный тип методу readValue, вы должны указать его конкретный тип как в параметре readValue parameterizedType, так и в его свойствах.

jacksonObjectMapper().readValue<Error>(response.body)

В строке выше вы должны заменить Error одним из его подтипов, который вам нужен, и в следующем вы должны заменить Map на HashedMap

data class MultiError(val errorMessage: Map<String, String>) : Error()

Другой способ заключается в использовании @JsonTypeInfo в объекте json для уведомления JackSon о включении точного типа объекта в качестве метаданных в поток json. Это решение требует исправления кода как на стороне сервера, так и на стороне клиента. Обратитесь к следующей ссылке:

https://fasterxml.github.io/jackson-annotations/javadoc/2.4/com/fasterxml/jackson/annotation/JsonTypeInfo.html

0
Mahdi Rajabi 21 Авг 2018 в 07:48

Проблема в том, что Джексону нужен JsonCreator или пустой конструктор.

Классы данных не имеют пустых конструкторов. В любом случае, если вам нужен пустой конструктор, вы можете использовать Плагин Kotlin No-Args.

Gradle:

 dependencies {
 classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlinVersion"
     ... 
 }
 ...
 apply plugin: "kotlin-noarg"
 ...
 noArg {
    annotation("com.your.package.NoArgs")
 }

Теперь создайте пользовательскую аннотацию:

package com.your.package

annotation class NoArgs

Теперь проще всего * - использовать универсальный тип в качестве сообщения об ошибке и использовать пользовательскую аннотацию:

@NoArgs
data class Error<out T>(val errorMessage: T)

val x = ObjectMapper().readValue<Error<*>>(json1)
val y = ObjectMapper().readValue<Error<*>>(json2)

when(y.errorMessage) {
  is String -> println("I'm a String!")
  is Map<*,*> -> println("I'm a Map")
  else -> println("I'm something else!")
}

Вы также можете явно определить тип в readValue, если знаете его заранее: readValue<Error<String>>

Вы также можете создать метод внутри Error для этой логики в when


*

0
Lovis 21 Авг 2018 в 08:19
51943696