在我的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
有很多方法可以(动态地)包含/排除字段及其值,但是没有一种“官方”方式可以解决这个问题:
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
注释实际上做我想要的,但这太容易忘记和麻烦。Optional<?>
应该允许每种类型的MixIn
注释的全局定义,但显然不适用(如上面的示例测试)。JsonInclude
(和相关的)注释是静态的,不关心字段的值。@JsonIgnore
字段的每个类上设置@JsonFilter
,您需要知道Optional
中的每个受影响的类型。即甚至不仅仅是在每个PropertyFilter
领域添加JsonInclude
。Optional
不允许基于给定bean实例的字段值动态包含/排除字段。@JsonView
注册的自定义JsonSerializer<?>
仅在插入字段名称后调用,即如果我们不执行任何操作,则生成的JSON无效。ObjectMapper.setNullValueSerializer()
,但它无法访问字段的值。到目前为止我能够调查,问题不在于混合,而是BeanSerializerModifier
的语义。对于类型的包含附件的含义有两种可能的解释:
这里的期望似乎是(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
属性被过滤掉。
Edit:根据StaxMan的回答,这似乎不是一个错误,而是一个功能,因为混合不是为某种类型的每个属性添加注释。使用问题中描述的混合的尝试只是在
Address
类上添加了@JsonInclude
注释,它具有不同的含义(已在其他答案中描述)。
由于mix-ins的设计行为不同,因此在Optional
的ObjectMapper
上有一个新的配置选项:
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;
}
一个工作的解决方案是覆盖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()