【问题标题】:Jackson: differing JsonInclude for all properties of certain type (e.g. Optional)Jackson:对于特定类型的所有属性(例如可选),不同的 JsonInclude
【发布时间】:2017-06-08 07:00:56
【问题描述】:

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

为了允许通过应用程序的 Rest API 进行部分更新,我还想区分设置为 null 的值和保持不变的值。为此,Java8 Optional<?> 类似乎是正确的选择。

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

开箱即用的反序列化行为正是我想要的。不存在的字段保持其默认值(此处为:null),并且显式提供的 null 值被反序列化为 Optional.empty()(或 Optional.ofNullable(null))。


我想要的是从生成的 JSON 中排除具有显式值 nullOptional<?> 字段,但始终包含任何其他字段(例如普通的 Integer)(甚至如果是null)。

MixIn 是众多可用选项之一。不幸的是,MixIn 可能适用于其他注释,但不适用于 @JsonInclude(这似乎是 Jackson 中的一个错误)。


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

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

  1. 每个 Optional&lt;?&gt; 字段上的 @JsonInclude 注释实际上可以满足我的需求,但这太容易忘记和麻烦了。
  2. 自定义 MixIn 应允许对每种类型的 JsonInclude 注释进行全局定义,但显然未应用(根据上述示例测试)。
  3. @JsonIgnore(及相关)注解是静态的,不关心字段的值。
  4. @JsonFilter 需要在每个包含Optional 字段的类上设置,并且您需要知道PropertyFilter 中每个受影响的类型。 IE。甚至比在每个 Optional 字段上添加 JsonInclude 更有价值。
  5. @JsonView 不允许基于给定 bean 实例的字段值动态包含/排除字段。
  6. 通过ObjectMapper.setNullValueSerializer()注册的自定义JsonSerializer&lt;?&gt;仅在插入字段名称后调用,即如果我们什么都不做,生成的JSON是无效的。
  7. 在将字段名称插入 JSON 之前涉及自定义 BeanSerializerModifier,但它无权访问该字段的值。

【问题讨论】:

    标签: java serialization java-8 jackson optional


    【解决方案1】:
    编辑:

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


    jackson-databind 2.9.0 版的解决方案

    由于混音被设计成不同的行为,ObjectMapperconfigOverride() 上有一个新的配置选项:

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

    调整后的示例如下所示:


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

    jackson-databind 2.9.0 版之前的解决方法

    一个可行的解决方案是覆盖 JacksonAnnotationIntrospector 并将其设置在 ObjectMapper 上。

    只需包含自定义自省类并将initObjectMapper() 方法更改为以下内容,给定的测试就会成功:

    public static class OptionalAwareAnnotationIntrospector
            extends JacksonAnnotationIntrospector {
        @Override
        public JsonInclude.Value findPropertyInclusion(Annotated a) {
            if (Optional.class.equals(a.getRawType())) {
                return JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_NULL);
            }
            return super.findPropertyInclusion(a);
        }
    }
    
    private ObjectMapper initObjectMapper() {
        return new ObjectMapper()
                .registerModule(new Jdk8Module())
                .setSerialisationInclusion(JsonInclude.Include.ALWAYS)
                .setAnnotationIntrospector(new OptionalAwareAnnotationIntrospector());
    }
    

    【讨论】:

    • 将此记录为issue #1522 for jackson-databind。让我们看看那里是否可以轻松修复。
    • jackson-databind 2.9.0 已于 2017 年 7 月底发布,并根据链接的 GitHub 问题 #1522 包含一个新的配置选项来支持此功能
    【解决方案2】:

    据我调查,问题根本不在于混音,而在于@JsonInclude 的语义。类型的包含附件的含义有两种可能的解释:

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

    这里的期望似乎是 (1),但由于历史原因,杰克逊实际上是 (2)。这允许轻松默认给定 POJO 的所有属性,例如:

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

    但不是,例如

    @JsonInclude(Include.NON_NULL)
    public class Address {
    }
    

    过滤掉所有null-valued Address-properties。

    【讨论】:

    • 很高兴在这里消除了我的误解。现在我只需要一种“正确”的方式来实现我的目标:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-01-14
    • 1970-01-01
    • 1970-01-01
    • 2020-12-06
    • 1970-01-01
    • 2021-02-17
    • 2021-10-09
    相关资源
    最近更新 更多