如何在Jackson JSON(反)序列化中自定义序列化或转换具有自定义键类型的Map属性?

时间:2018-03-10 16:06:38

标签: java json jackson jackson-databind

我正在序列化

的实例
@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$1.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$3.run(ParentRunner.java:290)
 at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
 at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
 at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
 at org.junit.runners.ParentRunner$2.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的序列化可能会有进一步的问题,我将通过使用不同的类来克服,最终我自己的。

可以在https://gitlab.com/krichter/jackson-map-custom-serializer找到SSCCE。

我正在使用Jackson 2.9.4。

1 个答案:

答案 0 :(得分:3)

这里的问题是当你使用Map.Entry时,密钥必须是一个字符串,因为它被序列化为{"key": value}

您有两个选择

您的第一个选项如果您可以将对象序列化为字符串,则可以将其用作json键。

在两种情况下,当对象包含单个字段(如示例中的字段)时,这是可行的。 e.g。

new SingleFieldObject(2l) // can be serialized as "2"

或者当constains可以表示为字符串的多个字段时。 e.g。

new MultipleFieldObject("John", 23) // can be serialized as "John 23 Years Old"

现在自定义对象可以表示为字符串,您可以使用地图或条目列表。

要使用简单的地图,只需使用属性&#39; keyUsing&#39;在注释中,您还必须定义自定义序列化程序和反序列化程序。

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
    }
}

要使用列表,您可以使用示例中的转换器,而是使用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都可能是一个复杂的对象。

您的第二个选项是使用自定义对象,您还有多个选项,一个对象包含键的所有字段以及值的字段/字段。

// ... 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 },
    }
}