自定义序列化器中的 Jackson TypeSerializer

问题描述 投票:0回答:1

我想创建一个自定义注释来将

Map
实例序列化为 json 数组,其中的键和值字段可由用户自定义。例如:
@JsonMap(key = "person", value = "address") Map<Person, Address>
应生成具有以下结构的数组:

[
  {
    "person": { },
    "address": { }
  },
  {
    "person": { },
    "address": { }
  }
]

这是我当前的设置,其工作原理与描述的完全一样:

fun main() {
    val mapper = jacksonObjectMapper()

    val data = MyData(mapOf(
        Person("John Doe", 25) to Address("Main Street 123", "New York")
    ))

    println(mapper.writeValueAsString(data)) // {"properties":[{"person":{"name":"John Doe","age":25},"address":{"street":"Main Street 123","city":"New York"}}]}
}

data class MyData(
    @field:JsonMap(key = "person", value = "address")
    val properties: Map<Person, Address>
)

data class Person(
    val name: String,
    val age: Int
)

data class Address(
    val street: String,
    val city: String
)

@Target(AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = JsonMapSerializer::class)
annotation class JsonMap(val key: String, val value: String)

class JsonMapSerializer : JsonSerializer<Map<*, *>>(), ContextualSerializer {
    private lateinit var keyField: String
    private lateinit var valueField: String

    override fun serialize(value: Map<*, *>, gen: JsonGenerator, serializers: SerializerProvider) {
        gen.writeStartArray()
        for ((k, v) in value) {
            gen.writeStartObject()
            gen.writePOJOField(keyField, k)
            gen.writePOJOField(valueField, v)
            gen.writeEndObject()
        }
        gen.writeEndArray()
    }

    override fun createContextual(prov: SerializerProvider, property: BeanProperty): JsonSerializer<*> {
        val annotation = property.getAnnotation(JsonMap::class.java)
            ?: throw JsonMappingException.from(prov, "Missing @JsonMap annotation")

        keyField = annotation.key
        valueField = annotation.value
        return this
    }
}

问题是现在我想包括

@JsonTypeInfo

data class MyData(
    @field:JsonMap(key = "person", value = "address")
    @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
    val properties: Map<Person, Address>
)

我希望输出是:

[
  {
    "person": {
      "@class": "com.example.Person",
      "name": "John Doe",
      "age": 25
    },
    "address": {
      "@class": "com.example.Address",
      "street": "Main Street 123",
      "city": "New York"
    }
  }
]

目前,添加

@JsonTypeInfo
注释不会以任何方式影响序列化。我无法弄清楚杰克逊执行此操作的惯用方式是什么,并且我找不到有关此主题的文档。我尝试从
createContextual
获取它:
prov.findTypeSerializer(property.type)
但返回
null

如果在属性上配置了

TypeSerializer
,然后在序列化时将其应用于每个映射条目的键和值,如何获取它?

kotlin jackson jackson-databind
1个回答
0
投票

我自己设法解决了这个问题。最终的解决方案有点复杂,因为

JsonTypeInfo
可以单独定义以应用于键和值,并覆盖它们中的每一个,以防它们被区别对待。

我的最终解决方案,以防将来对某人有所帮助:

fun main() {
    val mapper = jacksonObjectMapper()

    println(mapper.writeValueAsString(MyData(mapOf(
        Person("John Doe", 25) to Address("Main Street 123", "New York"),
        null to Address("Main Street 123", "New York"),
        Person("Jane Doe", 21) to null
    ))))

    println(mapper.writeValueAsString(MyDataSimple(mapOf(
        Person("John Doe", 25) to 16,
        null to 22,
        Person("Jane Doe", 21) to null
    ))))
}

data class MyData(
    @field:JsonMap(
        key = JsonMap.Entry(field = "person", type =  JsonTypeInfo(use = CLASS)),
        value = JsonMap.Entry(field = "address")
    )
    val properties: Map<Person?, Address?>
)

data class MyDataSimple(
    @JsonTypeInfo(use = CLASS)
    @field:JsonMap
    val properties: Map<Person?, Int?>
)

data class Person(
    val name: String,
    val age: Int
)

data class Address(
    val street: String,
    val city: String
)

@Target(AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = JsonMapSerializer::class)
annotation class JsonMap(
    val key: Entry = Entry(field = "key"),
    val value: Entry = Entry(field = "value")
) {
    @Target(AnnotationTarget.ANNOTATION_CLASS)
    @Retention(AnnotationRetention.RUNTIME)
    annotation class Entry(
        val field: String,
        val type: JsonTypeInfo = JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
    )
}

class JsonMapSerializer : JsonSerializer<Map<*, *>>(), ContextualSerializer {
    private lateinit var key: EntryConfiguration
    private lateinit var value: EntryConfiguration

    override fun serialize(map: Map<*, *>, gen: JsonGenerator, serializers: SerializerProvider) {
        gen.writeStartArray()
        for ((k, v) in map) {
            gen.writeStartObject()
            serialize(key, k, gen, serializers)
            serialize(value, v, gen, serializers)
            gen.writeEndObject()
        }
        gen.writeEndArray()
    }

    override fun createContextual(prov: SerializerProvider, property: BeanProperty): JsonSerializer<*> {
        val annotation = property.getAnnotation(JsonMap::class.java)
        val typeId = property.getAnnotation(JsonTypeInfo::class.java)

        key = create(annotation.key, typeId ?: annotation.key.type, prov, property)
        value = create(annotation.value, typeId ?: annotation.value.type, prov, property)
        return this
    }

    private fun create(
        entry: JsonMap.Entry,
        typeId: JsonTypeInfo,
        prov: SerializerProvider,
        property: BeanProperty
    ) = EntryConfiguration(
        field = entry.field,
        typeSerializer = typeId.takeIf { it.use != NONE }?.let {
            StdTypeResolverBuilder()
                .withSettings(from(it))
                .buildTypeSerializer(prov.config, property.type, emptySet())
        }
    )

    private fun serialize(
        entry: EntryConfiguration,
        value: Any?,
        gen: JsonGenerator,
        serializers: SerializerProvider
    ) = when {
        value == null -> gen.writeNullField(entry.field)
        value::class.javaPrimitiveType != null || value::class.java.isEnum -> gen.writePOJOField(entry.field, value)
        entry.typeSerializer != null -> entry.typeSerializer.writeTypedValue(entry.field, value, gen, serializers)
        else -> gen.writePOJOField(entry.field, value)
    }

    private fun TypeSerializer.writeTypedValue(
        field: String,
        value: Any,
        gen: JsonGenerator,
        serializers: SerializerProvider
    ) {
        gen.writeFieldName(field)
        val typeId = writeTypePrefix(gen, typeId(value, JsonToken.START_OBJECT))
        val serializer = serializers.findValueSerializer(value::class.java)
        serializer.unwrappingSerializer(NameTransformer.NOP).serialize(value, gen, serializers)
        writeTypeSuffix(gen, typeId)
    }

    private data class EntryConfiguration(
        val field: String,
        val typeSerializer: TypeSerializer?
    )
}

这似乎适用于我的用例,但它可能没有涵盖它应该涵盖的所有用例......

© www.soinside.com 2019 - 2024. All rights reserved.