简体   繁体   English

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

[英]How to customly serialize or convert a Map property with custom key type in Jackson JSON (de-)serialization?

I'm serializing instances of 我正在序列化实例

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

and

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

with

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 and @JsonDeserialize have been added in order to be able to serialize the map with complex key type. 添加了@JsonSerialize@JsonDeserialize ,以便能够使用复杂的键类型序列化地图。 The converters are 转换器是

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

and

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());
    }
}

However, the annotations have no effect since the deserialization still fails due to 但是,注释没有任何效果,因为反序列化仍然会失败

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)

I took a look into map serialization and am pretty sure I understood the basic concept and expect a key serializer to be unnecessary because the conversion takes place first and the converted output is a list which doesn't need one. 我看了一下地图序列化,我非常肯定我理解了基本概念,并期望一个关键的序列化器是不必要的,因为转换首先发生,转换后的输出是一个不需要的列表。

There might be further issues with the serialization of Entry which I will then overcome by using a different class, eventually my own. Entry的序列化可能还有其他问题,我将通过使用不同的类来克服,最终是我自己的。

A SSCCE can be found at https://gitlab.com/krichter/jackson-map-custom-serializer . 可以在https://gitlab.com/krichter/jackson-map-custom-serializer找到SSCCE。

I'm using Jackson 2.9.4. 我正在使用Jackson 2.9.4。

The problem here is that when you use Map.Entry the key has to be a string, because it gets serialized like {"key": value} . 这里的问题是当你使用Map.Entry时 ,键必须是一个字符串,因为它被序列化为{"key": value}


You have two options 你有两个选择


Your first option if you can serialize your object as string you can use it as the json key. 如果您可以将对象序列化为字符串,则可以将其用作json键,这是您的第一个选项

This is posible in two cases, when the object contains a single field (like the one in your example). 在两种情况下,当对象包含单个字段(如示例中的字段)时,这是可行的。 eg 例如

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

Or when constains multiple fields that can be represented as string. 或者当constains可以表示为字符串的多个字段时。 eg 例如

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

Now that the custom object can be represented as string you could use either a map or a list of entries. 既然自定义对象可以表示为字符串,您可以使用映射或条目列表。

To use a simple map just use the attribute 'keyUsing' in the annotations, and also you have to define the custom serializer and deserializer. 要使用简单的地图,只需在注释中使用属性“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());
    }
}

Then you annotate the field with your serializer and deserializer: 然后用序列化器和反序列化器注释该字段:

@JsonSerialize(keyUsing = MyKeySerializer.class) // no need of converter
@JsonDeserialize(keyUsing = MyKeyDeserializer.class) // no need of converter
private Map<Entity2, Integer> valueMap = new HashMap<>();

Using this object. 使用此对象。

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);

A JSON like this is generated 生成这样的JSON

{
    "id": 1,
    "valueMap": {
        "2": 21,
        "3": 22
    }
}

To use a list you could use the converters in your example, but instead Entity2 you return a String for the key. 要使用列表,您可以使用示例中的转换器,而是使用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;
    }
}

A JSON like this is generated 生成这样的JSON

{
    "id": 1,
    "valueMap": [
        { "2": 21 },
        { "3": 22 }
    ]
}

In both cases the value Integer could be a complex object. 在这两种情况下,值Integer都可能是一个复杂的对象。


Your second option is to use a custom object, again you have multiple options, one object that hold all the fields of the key and the field/fields of the value. 您的第二个选择是使用自定义对象,同样您有多个选项,一个对象包含键的所有字段和值的字段/字段。

// ... serialization - deserialization of the object
public class CustomObject {
    private Long id; // ... all key fields
    private int value; // ... all value fields
}

Then you use the converters public class ValueListMapConverter extends StdConverter<List<CustomObject>, Map<Entity2, Integer>> and public class ValueMapMapConverter extends StdConverter<Map<Entity2, Integer>, List<CustomObject>> 然后使用转换器public class ValueListMapConverter extends StdConverter<List<CustomObject>, Map<Entity2, Integer>>public class ValueMapMapConverter extends StdConverter<Map<Entity2, Integer>, List<CustomObject>>

This generates a JSON like this 这会生成一个像这样的JSON

{
    "id": 1,
    "valueMap": [
        { "id": 2, "value": 21 },
        { "id": 3, "value": 22 }
    ]
}

You could use a map instead a list and use a key, and the rest of the fields of the key object, together with the value fields in a custom object. 您可以使用映射而不是列表,并使用键,键对象的其余字段以及自定义对象中的值字段。

// ... serialization - deserialization of the object
public class CustomObject {
    // ... rest of the key fields
    private int value; // ... all value fields
}

The converters public class ValueListMapConverter extends StdConverter<Map<Long, CustomObject>, Map<Entity2, Integer>> and public class ValueMapMapConverter extends StdConverter<Map<Entity2, Integer>, Map<Long, CustomObject>> 转换器public class ValueListMapConverter extends StdConverter<Map<Long, CustomObject>, Map<Entity2, Integer>>public class ValueMapMapConverter extends StdConverter<Map<Entity2, Integer>, Map<Long, CustomObject>>

This generates a JSON like this 这会生成一个像这样的JSON

{
    "id": 1,
    "valueMap": {
        "2": { "value": 21 },
        "3": { "value": 22 },
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM