杰克逊:对于某种类型的所有属性,不同的JsonInclude(例如,可选)

问题描述 投票:1回答:2

在我的Java应用程序中使用Jackson进行序列化(POJO到JSON)和反序列化(JSON到POJO)时,我通常希望保留所有字段,因此使用(默认)qazxsw poi。

为了通过应用程序的Rest API允许部分更新,我还想区分一个特别设置为JsonInclude.Value.ALWAYS的值和保持不变的值。为此,Java8 null类似乎是正确的选择。

为了获得适当的支持,我必须将Optional<?>添加到Jdk8Module。一切都很简单。

开箱即用的反序列化行为正是我想要的。一个不存在的字段仍然是它的默认值(这里:ObjectMapper),一个明确提供的null值被反​​序列化为null(或Optional.empty())。


我想要的是一个Optional.ofNullable(null)字段,其中Optional<?>的显式值被排除在生成的JSON之外,但是总是包含任何其他字段(例如普通的null)(即使它是Integer)。

其中一个选择是null。不幸的是,MixIn可能适用于其他注释,但不适用于MixIn(这似乎是杰克逊的一个错误)。


@JsonInclude

有很多方法可以(动态地)包含/排除字段及其值,但是没有一种“官方”方式可以解决这个问题:

  1. 每个public class OptionalObjectMappingTest { public static class MyBean { public Integer one; public Optional<Integer> two; public Optional<Integer> three; public Optional<Integer> four; } @JsonInclude(JsonInclude.Include.NOT_NULL) public static class OptionalMixIn {} private ObjectMapper initObjectMapper() { return new ObjectMapper() .registerModule(new Jdk8Module()) .setSerialisationInclusion(JsonInclude.Include.ALWAYS) .addMixIn(Optional.class, OptionalMixIn.class); } @Test public void testDeserialisation() { String json = "{\"one\":null,\"two\":2,\"three\":null}"; MyBean bean = initObjectMapper().readValue(json, MyBean.class); Assert.assertNull(bean.one); Assert.assertEquals(Optional.of(2), bean.two); Assert.assertEquals(Optional.empty(), bean.three); Assert.assertNull(bean.four); } @Test public void testSerialisation() { MyBean bean = new MyBean(); bean.one = null; bean.two = Optional.of(2); bean.three = Optional.empty(); bean.four = null; String result = initObjectMapper().writeValueAsString(bean); String expected = "{\"one\":null,\"two\":2,\"three\":null}"; // FAILS, due to result = "{one:null,two:2,three:null,four:null}" Assert.assertEquals(expected, result); } } 领域的@JsonInclude注释实际上做我想要的,但这太容易忘记和麻烦。
  2. 自定义Optional<?>应该允许每种类型的MixIn注释的全局定义,但显然不适用(如上面的示例测试)。
  3. JsonInclude(和相关的)注释是静态的,不关心字段的值。
  4. 需要在包含@JsonIgnore字段的每个类上设置@JsonFilter,您需要知道Optional中的每个受影响的类型。即甚至不仅仅是在每个PropertyFilter领域添加JsonInclude
  5. Optional不允许基于给定bean实例的字段值动态包含/排除字段。
  6. 通过@JsonView注册的自定义JsonSerializer<?>仅在插入字段名称后调用,即如果我们不执行任何操作,则生成的JSON无效。
  7. 在将字段的名称插入JSON之前,会涉及自定义ObjectMapper.setNullValueSerializer(),但它无法访问字段的值。
java serialization java-8 jackson optional
2个回答
1
投票

到目前为止我能够调查,问题不在于混合,而是BeanSerializerModifier的语义。对于类型的包含附件的含义有两种可能的解释:

  1. 对所有类型的值使用该包含(除非被每个属性注释覆盖)
  2. 为所有类型定义的POJO属性使用该包含(除非被每个属性注释覆盖)

这里的期望似乎是(1),但杰克逊实际上(2)出于历史原因。这允许容易地违反给定POJO的所有属性,例如:

@JsonInclude

但不是,例如

@JsonInclude(Include.NON_NULL)
public class Stuff {
    public String name;
    public Address address;
}

使所有@JsonInclude(Include.NON_NULL) public class Address { } null属性被过滤掉。


1
投票
Edit:

根据StaxMan的回答,这似乎不是一个错误,而是一个功能,因为混合不是为某种类型的每个属性添加注释。使用问题中描述的混合的尝试只是在Address类上添加了@JsonInclude注释,它具有不同的含义(已在其他答案中描述)。


Solution as of jackson-databind version 2.9.0

由于mix-ins的设计行为不同,因此在OptionalObjectMapper上有一个新的配置选项:

  • configOverride()

配置很简单:

setIncludeAsProperty()

调整后的示例如下所示:


private ObjectMapper initObjectMapper() {
    ObjectMapper objectMapper = new ObjectMapper()
            .registerModule(new Jdk8Module())
            .setSerializationInclusion(JsonInclude.Include.ALWAYS);
    objectMapper.configOverride(Optional.class)
            .setIncludeAsProperty(JsonInclude.Value
                    .construct(JsonInclude.Include.NON_NULL, null));
    return objectMapper;
}

Workaround before jackson-databind version 2.9.0

一个工作的解决方案是覆盖public class OptionalObjectMappingTest { public static class MyBean { public Integer one; public Optional<Integer> two; public Optional<Integer> three; public Optional<Integer> four; } private ObjectMapper initObjectMapper() { ObjectMapper objectMapper = new ObjectMapper() .registerModule(new Jdk8Module()) .setSerializationInclusion(JsonInclude.Include.ALWAYS); objectMapper.configOverride(Optional.class) .setIncludeAsProperty(JsonInclude.Value .construct(JsonInclude.Include.NON_NULL, null)); return objectMapper; } @Test public void testRoundTrip() throws Exception { String originalJson = "{\"one\":null,\"two\":2,\"three\":null}"; ObjectMapper mapper = initObjectMapper(); MyBean bean = mapper.readValue(originalJson, MyBean.class); String resultingJson = mapper.writeValueAsString(bean); // SUCCESS: no "four:null" field is being added Assert.assertEquals(originalJson, resultingJson); } } 并将其设置在JacksonAnnotationIntrospector上。

只需包含自定义的introspector类并将ObjectMapper方法更改为以下内容,并且给定的测试将成功:

initObjectMapper()
© www.soinside.com 2019 - 2024. All rights reserved.