【问题标题】:How to use Jackson custom value serializer for Map<String, Object>?如何为 Map<String, Object> 使用 Jackson 自定义值序列化程序?
【发布时间】:2017-09-19 07:58:54
【问题描述】:

我有一个来自 DB 的结果集,格式为 Map&lt;String, Object&gt;,我应该从 REST 服务返回为 json。映射中的值可以是各种类型,包括PGObjectStringIntegerDate

我为org.postgresql.util.PGObject 类编写了一个自定义序列化程序,类型为“jsonb”,在List&lt;?&gt; 中可以正常工作,但在Map&lt;String, Object&gt; 中却不行。

public class PgObjectSerializer extends JsonSerializer<PGobject>{

    @Override
    public void serialize(PGobject pgObject, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        switch (pgObject.getType()) {
            case "json":
            case "jsonb":
                gen.writeRawValue(pgObject.getValue());
                break;
            default:
                gen.writeString(pgObject.getValue());
        }
    }
}

目标 PGObject 如下所示:

PGObject pgo = new PGObject();
pgo.setType("jsonb");
pgo.setValue("[{"id": 6, "name": "Foo"}, {"id": 7, "name": "Bar"}, {"id": 8, "name": "Baz"}]"); map.put("reason", pgo);

当 Jackson 在 Map&lt;String, Object&gt; 中序列化这个 PGObject 值时,我会得到 json 值,例如:

  "reason": {
    "type": "jsonb",
    "value": "[{\"id\": 6, \"name\": \"Foo\"}, {\"id\": 7, \"name\": \"Bar\"}, {\"id\": 8, \"name\": \"Baz\"}]"
  }

我需要什么:

  "reason": [
    {
      "id": 6,
      "name": "Foo"
    },
    {
      "id": 7,
      "name": "Bar"
    },
    {
      "id": 8,
      "name": "Baz"
    },
  ],

我尝试将模块添加到ObjectMapper 并将自定义MapType 添加到ObjectWriter,如Serializing Map<Date, String> with Jackson 的答案所示:

@Service
@Transactional
public class MyClass {
    private final ObjectWriter writer;
    private final MyRepo repository;

    @Autowired
    public MyClass(MyRepo repository) {
        this.repository = repository;

        SimpleModule module = new SimpleModule();
        module.addKeySerializer(PGobject.class, new PgObjectSerializer());

        ObjectMapper mapper = new ObjectMapper();
        JavaType myMapType = mapper.getTypeFactory().
                constructMapType(HashMap.class, String.class, PGobject.class);
        writer = mapper.registerModule(module).writerFor(myMapType);
    }

    ...

    private String toJsonString(Map<String, Object> map) {
        try {
            return writer.writeValueAsString(map);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }
}

但是对于地图中的每个非PGObject 对象,我都会收到序列化错误:

[Test worker] ERROR 
com.fasterxml.jackson.databind.JsonMappingException: object is not an instance of declaring class (through reference chain: java.util.HashMap["end_date"]->java.lang.String["type"])
    at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:388)
    at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:348)
    at com.fasterxml.jackson.databind.ser.std.StdSerializer.wrapAndThrow(StdSerializer.java:343)
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:698)
    at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
    at com.fasterxml.jackson.databind.ser.std.MapSerializer.serializeFieldsUsing(MapSerializer.java:736)
    at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:534)
    at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:30)
    at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:416)
    at com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1425)
    at com.fasterxml.jackson.databind.ObjectWriter._configAndWriteValue(ObjectWriter.java:1158)
    at com.fasterxml.jackson.databind.ObjectWriter.writeValueAsString(ObjectWriter.java:1031)

当在Map&lt;String, Object&gt; 序列化过程中发现 PGObject 作为值时,如何在 Jackson 中启用我的 PGObjectSerializer

【问题讨论】:

  • 尝试使用 module.addSerializer() 而不是 addKeySerializer。我认为密钥序列化程序仅适用于密钥。
  • @oleg.cherednik,感谢您指出这一点,这就是原因!我已更改为 addSerializer,现在我的代码运行良好。

标签: java jackson


【解决方案1】:

一种解决方法是为Object 添加序列化程序。

然后在序列化程序本身中,您可以检查 Object 是否为 instanceOf PGobject

在 myMapType 中你可以指定Object.class:

JavaType myMapType = mapper.getTypeFactory().
                constructMapType(HashMap.class, String.class, Object.class);

序列化器将是:

class PgObjectSerializer extends JsonSerializer<Object> {
@Override
public void serialize(Object object, JsonGenerator gen, SerializerProvider serializers) throws IOException {
    if (object instanceof PGobject) {
        PGobject pgObject = (PGobject) object;
        switch (pgObject.getType()) {
        case "json":
        case "jsonb":
                gen.writeRawValue(pgObject.getType());
                break;
        default:
                gen.writeString(pgObject.getType());
        }
        }else{
                ObjectMapper mapper = new ObjectMapper();
                mapper.writeValue(gen, object);
        }
    }
}

【讨论】:

  • 此解决方法需要将所有其他类类型的序列化委托给其他一些“基本”或“通用”序列化程序。我没有找到这样的。
  • 你可以使用 obejct 映射器来处理那个东西,看我更新的 PgObjectSerializer
  • 不幸的是,else 部分在字符串密钥序列化过程中导致异常:Caused by: com.fasterxml.jackson.core.JsonGenerationException: Can not write a string, expecting field name (context: Object)。我试过serializers.defaultSerializeValue(value, gen)gen.writeString(value.toString()) 但得到了同样的错误。
  • 试试mapper.writeValue(gen, object);我也更新了回答
  • 从@oleg.cherednik 的评论中对module 进行了小修复后,此解决方法按预期工作。谢谢!
猜你喜欢
  • 1970-01-01
  • 2014-05-19
  • 2018-08-24
  • 1970-01-01
  • 2017-05-09
  • 2017-03-02
  • 2021-09-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多