如何在 Jackson JSON(反)序列化中使用自定义键类型自定义序列化或转换 Map 属性?
How to customly serialize or convert a Map property with custom key type in Jackson JSON (de-)serialization?
我正在序列化
的实例
@JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id",
scope=Entity1.class)
public class Entity1 {
private Long id;
@JsonSerialize(converter = ValueMapListConverter.class)
@JsonDeserialize(converter = ValueMapMapConverter.class)
private Map<Entity2, Integer> valueMap = new HashMap<>();
public Entity1() {
}
public Entity1(Long id) {
this.id = id;
}
[getter and setter]
}
和
@JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id",
scope=Entity2.class)
public class Entity2 {
private Long id;
public Entity2() {
}
public Entity2(Long id) {
this.id = id;
}
[getter and setter]
}
与
ObjectMapper objectMapper = new ObjectMapper();
Entity1 entity1 = new Entity1(1l);
Entity2 entity2 = new Entity2(2l);
entity1.getValueMap().put(entity2, 10);
String serialized = objectMapper.writeValueAsString(entity1);
Entity1 deserialized = objectMapper.readValue(serialized, Entity1.class);
assertEquals(entity1,
deserialized);
添加了 @JsonSerialize
和 @JsonDeserialize
以便能够序列化具有复杂键类型的地图。转换器是
public class ValueMapMapConverter extends StdConverter<List<Entry<Entity2, Integer>>, Map<Entity2, Integer>> {
@Override
public Map<Entity2, Integer> convert(List<Entry<Entity2, Integer>> value) {
Map<Entity2, Integer> retValue = new HashMap<>();
for(Entry<Entity2, Integer> entry : value) {
retValue.put(entry.getKey(), entry.getValue());
}
return retValue;
}
}
和
public class ValueMapListConverter extends StdConverter<Map<Entity2, Integer>, List<Entry<Entity2, Integer>>> {
@Override
public List<Entry<Entity2, Integer>> convert(Map<Entity2, Integer> value) {
return new LinkedList<>(value.entrySet());
}
}
但是,由于
反序列化仍然失败,因此注释无效
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot find a (Map) Key deserializer for type [simple type, class richtercloud.jackson.map.custom.serializer.Entity2]
at [Source: (String)"{"id":1,"valueMap":{"richtercloud.jackson.map.custom.serializer.Entity2@bb":10}}"; line: 1, column: 1]
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1451)
at com.fasterxml.jackson.databind.deser.DeserializerCache._handleUnknownKeyDeserializer(DeserializerCache.java:589)
at com.fasterxml.jackson.databind.deser.DeserializerCache.findKeyDeserializer(DeserializerCache.java:168)
at com.fasterxml.jackson.databind.DeserializationContext.findKeyDeserializer(DeserializationContext.java:500)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.createContextual(MapDeserializer.java:248)
at com.fasterxml.jackson.databind.DeserializationContext.handlePrimaryContextualization(DeserializationContext.java:651)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.resolve(BeanDeserializerBase.java:471)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:293)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:244)
at com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(DeserializerCache.java:142)
at com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(DeserializationContext.java:477)
at com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(ObjectMapper.java:4178)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3997)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2992)
at richtercloud.jackson.map.custom.serializer.TheTest.testSerialization(TheTest.java:29)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access[=17=]0(ParentRunner.java:58)
at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:252)
at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)
我查看了地图序列化,我很确定我理解了基本概念,并期望密钥序列化程序是不必要的,因为转换首先发生,转换后的输出是一个不需要序列化的列表。
Entry
的序列化可能还有其他问题,我将通过使用不同的 class(最终是我自己的)来克服这些问题。
可以在 https://gitlab.com/krichter/jackson-map-custom-serializer.
找到 SSCCE
我正在使用 Jackson 2.9.4。
这里的问题是,当您使用 Map.Entry 时,密钥必须是一个字符串,因为它像 {"key": value}
.
一样被序列化了
你有两个选择
您的第一个选择 如果您可以将对象序列化为字符串,则可以将其用作 json 键。
这在两种情况下是可行的,当对象包含单个字段(如您的示例中的字段)时。例如
new SingleFieldObject(2l) // can be serialized as "2"
或者当包含多个可以表示为字符串的字段时。例如
new MultipleFieldObject("John", 23) // can be serialized as "John 23 Years Old"
现在自定义对象可以表示为字符串,您可以使用映射或条目列表。
要使用简单的映射,只需在注释中使用属性 'keyUsing',还必须定义自定义序列化器和反序列化器。
public class MyKeyDeserializer extends KeyDeserializer {
@Override
public Entity2 deserializeKey(String key,
DeserializationContext ctxt) throws IOException {
return new Entity2(Long.parseLong(key));
}
}
public class MyKeySerializer extends JsonSerializer<Entity2> {
@Override
public void serialize(Entity2 value,
JsonGenerator gen,
SerializerProvider serializers) throws IOException {
gen.writeFieldName(value.getId().toString());
}
}
然后用序列化器和反序列化器注释该字段:
@JsonSerialize(keyUsing = MyKeySerializer.class) // no need of converter
@JsonDeserialize(keyUsing = MyKeyDeserializer.class) // no need of converter
private Map<Entity2, Integer> valueMap = new HashMap<>();
正在使用这个对象。
Entity1 entity1 = new Entity1(1l);
Entity2 entity2_1 = new Entity2(2l);
Entity2 entity2_2 = new Entity2(3l);
entity1.getValueMap().put(entity2_1, 21);
entity1.getValueMap().put(entity2_2, 22);
一个这样的JSON就生成了
{
"id": 1,
"valueMap": {
"2": 21,
"3": 22
}
}
要使用列表,您可以在您的示例中使用转换器,但是您 return 一个字符串作为键而不是 Entity2。
public class ValueMapListConverter
extends StdConverter<Map<Entity2, Integer>, List<Entry<String, Integer>>> {
@Override
public List<Entry<String, Integer>> convert(Map<Entity2, Integer> value) {
List<Entry<String, Integer>> result = new ArrayList<>();
for (Entry<Entity2, Integer> entry : value.entrySet()) {
result.add(new SimpleEntry<>(entry.getKey().getId().toString(),
entry.getValue()));
}
return result;
}
}
public class ValueMapMapConverter
extends StdConverter<List<Entry<String, Integer>>, Map<Entity2, Integer>> {
@Override
public Map<Entity2, Integer> convert(List<Entry<String, Integer>> value) {
Map<Entity2, Integer> retValue = new HashMap<>();
for(Entry<String, Integer> entry : value) {
retValue.put(new Entity2(Long.parseLong(entry.getKey())), entry.getValue());
}
return retValue;
}
}
一个这样的JSON就生成了
{
"id": 1,
"valueMap": [
{ "2": 21 },
{ "3": 22 }
]
}
在这两种情况下,值 Integer 都可以是一个复杂的对象。
您的第二个选择 是使用自定义对象,同样您有多个选项,一个对象包含键的所有字段和 field/fields值。
// ... serialization - deserialization of the object
public class CustomObject {
private Long id; // ... all key fields
private int value; // ... all value fields
}
然后你使用转换器
public class ValueListMapConverter extends StdConverter<List<CustomObject>, Map<Entity2, Integer>>
和 public class ValueMapMapConverter extends StdConverter<Map<Entity2, Integer>, List<CustomObject>>
这会生成一个 JSON 这样的
{
"id": 1,
"valueMap": [
{ "id": 2, "value": 21 },
{ "id": 3, "value": 22 }
]
}
您可以使用映射而不是列表并使用键、键对象的其余字段以及自定义对象中的值字段。
// ... serialization - deserialization of the object
public class CustomObject {
// ... rest of the key fields
private int value; // ... all value fields
}
转换器
public class ValueListMapConverter extends StdConverter<Map<Long, CustomObject>, Map<Entity2, Integer>>
和 public class ValueMapMapConverter extends StdConverter<Map<Entity2, Integer>, Map<Long, CustomObject>>
这会生成一个 JSON 这样的
{
"id": 1,
"valueMap": {
"2": { "value": 21 },
"3": { "value": 22 },
}
}
我正在序列化
的实例@JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id",
scope=Entity1.class)
public class Entity1 {
private Long id;
@JsonSerialize(converter = ValueMapListConverter.class)
@JsonDeserialize(converter = ValueMapMapConverter.class)
private Map<Entity2, Integer> valueMap = new HashMap<>();
public Entity1() {
}
public Entity1(Long id) {
this.id = id;
}
[getter and setter]
}
和
@JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id",
scope=Entity2.class)
public class Entity2 {
private Long id;
public Entity2() {
}
public Entity2(Long id) {
this.id = id;
}
[getter and setter]
}
与
ObjectMapper objectMapper = new ObjectMapper();
Entity1 entity1 = new Entity1(1l);
Entity2 entity2 = new Entity2(2l);
entity1.getValueMap().put(entity2, 10);
String serialized = objectMapper.writeValueAsString(entity1);
Entity1 deserialized = objectMapper.readValue(serialized, Entity1.class);
assertEquals(entity1,
deserialized);
添加了 @JsonSerialize
和 @JsonDeserialize
以便能够序列化具有复杂键类型的地图。转换器是
public class ValueMapMapConverter extends StdConverter<List<Entry<Entity2, Integer>>, Map<Entity2, Integer>> {
@Override
public Map<Entity2, Integer> convert(List<Entry<Entity2, Integer>> value) {
Map<Entity2, Integer> retValue = new HashMap<>();
for(Entry<Entity2, Integer> entry : value) {
retValue.put(entry.getKey(), entry.getValue());
}
return retValue;
}
}
和
public class ValueMapListConverter extends StdConverter<Map<Entity2, Integer>, List<Entry<Entity2, Integer>>> {
@Override
public List<Entry<Entity2, Integer>> convert(Map<Entity2, Integer> value) {
return new LinkedList<>(value.entrySet());
}
}
但是,由于
反序列化仍然失败,因此注释无效com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot find a (Map) Key deserializer for type [simple type, class richtercloud.jackson.map.custom.serializer.Entity2]
at [Source: (String)"{"id":1,"valueMap":{"richtercloud.jackson.map.custom.serializer.Entity2@bb":10}}"; line: 1, column: 1]
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1451)
at com.fasterxml.jackson.databind.deser.DeserializerCache._handleUnknownKeyDeserializer(DeserializerCache.java:589)
at com.fasterxml.jackson.databind.deser.DeserializerCache.findKeyDeserializer(DeserializerCache.java:168)
at com.fasterxml.jackson.databind.DeserializationContext.findKeyDeserializer(DeserializationContext.java:500)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.createContextual(MapDeserializer.java:248)
at com.fasterxml.jackson.databind.DeserializationContext.handlePrimaryContextualization(DeserializationContext.java:651)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.resolve(BeanDeserializerBase.java:471)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:293)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:244)
at com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(DeserializerCache.java:142)
at com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(DeserializationContext.java:477)
at com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(ObjectMapper.java:4178)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3997)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2992)
at richtercloud.jackson.map.custom.serializer.TheTest.testSerialization(TheTest.java:29)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access[=17=]0(ParentRunner.java:58)
at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:252)
at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)
我查看了地图序列化,我很确定我理解了基本概念,并期望密钥序列化程序是不必要的,因为转换首先发生,转换后的输出是一个不需要序列化的列表。
Entry
的序列化可能还有其他问题,我将通过使用不同的 class(最终是我自己的)来克服这些问题。
可以在 https://gitlab.com/krichter/jackson-map-custom-serializer.
找到 SSCCE我正在使用 Jackson 2.9.4。
这里的问题是,当您使用 Map.Entry 时,密钥必须是一个字符串,因为它像 {"key": value}
.
你有两个选择
您的第一个选择 如果您可以将对象序列化为字符串,则可以将其用作 json 键。
这在两种情况下是可行的,当对象包含单个字段(如您的示例中的字段)时。例如
new SingleFieldObject(2l) // can be serialized as "2"
或者当包含多个可以表示为字符串的字段时。例如
new MultipleFieldObject("John", 23) // can be serialized as "John 23 Years Old"
现在自定义对象可以表示为字符串,您可以使用映射或条目列表。
要使用简单的映射,只需在注释中使用属性 'keyUsing',还必须定义自定义序列化器和反序列化器。
public class MyKeyDeserializer extends KeyDeserializer {
@Override
public Entity2 deserializeKey(String key,
DeserializationContext ctxt) throws IOException {
return new Entity2(Long.parseLong(key));
}
}
public class MyKeySerializer extends JsonSerializer<Entity2> {
@Override
public void serialize(Entity2 value,
JsonGenerator gen,
SerializerProvider serializers) throws IOException {
gen.writeFieldName(value.getId().toString());
}
}
然后用序列化器和反序列化器注释该字段:
@JsonSerialize(keyUsing = MyKeySerializer.class) // no need of converter
@JsonDeserialize(keyUsing = MyKeyDeserializer.class) // no need of converter
private Map<Entity2, Integer> valueMap = new HashMap<>();
正在使用这个对象。
Entity1 entity1 = new Entity1(1l);
Entity2 entity2_1 = new Entity2(2l);
Entity2 entity2_2 = new Entity2(3l);
entity1.getValueMap().put(entity2_1, 21);
entity1.getValueMap().put(entity2_2, 22);
一个这样的JSON就生成了
{
"id": 1,
"valueMap": {
"2": 21,
"3": 22
}
}
要使用列表,您可以在您的示例中使用转换器,但是您 return 一个字符串作为键而不是 Entity2。
public class ValueMapListConverter
extends StdConverter<Map<Entity2, Integer>, List<Entry<String, Integer>>> {
@Override
public List<Entry<String, Integer>> convert(Map<Entity2, Integer> value) {
List<Entry<String, Integer>> result = new ArrayList<>();
for (Entry<Entity2, Integer> entry : value.entrySet()) {
result.add(new SimpleEntry<>(entry.getKey().getId().toString(),
entry.getValue()));
}
return result;
}
}
public class ValueMapMapConverter
extends StdConverter<List<Entry<String, Integer>>, Map<Entity2, Integer>> {
@Override
public Map<Entity2, Integer> convert(List<Entry<String, Integer>> value) {
Map<Entity2, Integer> retValue = new HashMap<>();
for(Entry<String, Integer> entry : value) {
retValue.put(new Entity2(Long.parseLong(entry.getKey())), entry.getValue());
}
return retValue;
}
}
一个这样的JSON就生成了
{
"id": 1,
"valueMap": [
{ "2": 21 },
{ "3": 22 }
]
}
在这两种情况下,值 Integer 都可以是一个复杂的对象。
您的第二个选择 是使用自定义对象,同样您有多个选项,一个对象包含键的所有字段和 field/fields值。
// ... serialization - deserialization of the object
public class CustomObject {
private Long id; // ... all key fields
private int value; // ... all value fields
}
然后你使用转换器
public class ValueListMapConverter extends StdConverter<List<CustomObject>, Map<Entity2, Integer>>
和 public class ValueMapMapConverter extends StdConverter<Map<Entity2, Integer>, List<CustomObject>>
这会生成一个 JSON 这样的
{
"id": 1,
"valueMap": [
{ "id": 2, "value": 21 },
{ "id": 3, "value": 22 }
]
}
您可以使用映射而不是列表并使用键、键对象的其余字段以及自定义对象中的值字段。
// ... serialization - deserialization of the object
public class CustomObject {
// ... rest of the key fields
private int value; // ... all value fields
}
转换器
public class ValueListMapConverter extends StdConverter<Map<Long, CustomObject>, Map<Entity2, Integer>>
和 public class ValueMapMapConverter extends StdConverter<Map<Entity2, Integer>, Map<Long, CustomObject>>
这会生成一个 JSON 这样的
{
"id": 1,
"valueMap": {
"2": { "value": 21 },
"3": { "value": 22 },
}
}