我想创建一个自定义注释来将
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
,然后在序列化时将其应用于每个映射条目的键和值,如何获取它?
我自己设法解决了这个问题。最终的解决方案有点复杂,因为
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?
)
}
这似乎适用于我的用例,但它可能没有涵盖它应该涵盖的所有用例......