如何将 JSON 对象反序列化为 Kotlin Pair 与 Jackson?

Jen*_*Jen 5 jackson kotlin

我有一个代表城市的简单数据类

data class City(
    val name: String,
    val centroid: Coordinates
)
Run Code Online (Sandbox Code Playgroud)

出于外部兼容性的原因,该Coordinates类型被定义为typealias

typealias Coordinates = Pair<Double, Double>

val Coordinates.lat
    get() = first

val Coordinates.lon
    get() = second
Run Code Online (Sandbox Code Playgroud)

如何配置 Jackson,以便它能够将以下 JSON 反序列化为 的实例City

{
  "name": "Praha",
  "centroid": {
    "lat": 50.2141,
    "lon": 14.42342
  }
}
Run Code Online (Sandbox Code Playgroud)

Fel*_*vic 5

如果您确实必须保持兼容性,您可以编写自己的序列化器和反序列化器,例如:

object CoordinatesConversions {

    const val LATITUDE_FIELD_NAME = "lat"
    const val LONGITUDE_FIELD_NAME = "lon"

    object Serializer : JsonSerializer<Coordinates>() {
        override fun serialize(value: Coordinates, gen: JsonGenerator, serializers: SerializerProvider) {
            with(gen) {
                writeStartObject()
                writeNumberField(LATITUDE_FIELD_NAME, value.first)
                writeNumberField(LONGITUDE_FIELD_NAME, value.second)
                writeEndObject()
            }
        }
    }

    object Deserializer : JsonDeserializer<Coordinates>() {
        override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Coordinates {
            val node = p.readValueAsTree<JsonNode>()

            val lat = node.get(LATITUDE_FIELD_NAME).asDouble()
            val lon = node.get(LONGITUDE_FIELD_NAME).asDouble()

            return Coordinates(lat, lon)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

还指定您的城市模型应使用哪些序列化器

data class City(
    val name: String,

    @JsonSerialize(using = CoordinatesConversions.Serializer::class)
    @JsonDeserialize(using = CoordinatesConversions.Deserializer::class)
    val center: Coordinates
)

Run Code Online (Sandbox Code Playgroud)

一个完整的工作示例:

import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue

private typealias Coordinates = Pair<Double, Double>

object CoordinatesConversions {

    const val LATITUDE_FIELD_NAME = "lat"
    const val LONGITUDE_FIELD_NAME = "lon"

    object Serializer : JsonSerializer<Coordinates>() {
        override fun serialize(value: Coordinates, gen: JsonGenerator, serializers: SerializerProvider) {
            with(gen) {
                writeStartObject()
                writeNumberField(LATITUDE_FIELD_NAME, value.first)
                writeNumberField(LONGITUDE_FIELD_NAME, value.second)
                writeEndObject()
            }
        }
    }

    object Deserializer : JsonDeserializer<Coordinates>() {
        override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Coordinates {
            val node = p.readValueAsTree<JsonNode>()

            val lat = node.get(LATITUDE_FIELD_NAME).asDouble()
            val lon = node.get(LONGITUDE_FIELD_NAME).asDouble()

            return Coordinates(lat, lon)
        }
    }
}

object StackOverflow {

    private val objectMapper = jacksonObjectMapper().apply {
        configure(SerializationFeature.INDENT_OUTPUT, true)
    }

    data class City(
        val name: String,

        @JsonSerialize(using = CoordinatesConversions.Serializer::class)
        @JsonDeserialize(using = CoordinatesConversions.Deserializer::class)
        val center: Coordinates
    )

    @JvmStatic
    fun main(args: Array<String>) {

        val city = City("Cair Paravel", 13.37 to 42.0)

        val jsonOutput = objectMapper.writeValueAsString(city)
        println(jsonOutput)

        val deserializedCity = objectMapper.readValue<City>(jsonOutput)
        println(deserializedCity)
    }
}
Run Code Online (Sandbox Code Playgroud)

我仍然认为最好尝试摆脱使用 Pairs 来表示这种具体的数据结构,并且我认为这对您将来会有好处。

例如,在表示地理点时,并不总是清楚使用哪种标准,一些供应商使用(lat, lng),其他供应商使用(lng, lat)。即使您在别名之上定义了命名属性 getter,您也不需要经历定义自定义序列化器和反序列化器的麻烦,这总是很好的。