在我的Java应用程序中使用Jackson进行序列化(POJO到JSON)和反序列化(JSON到POJO)时,我通常希望保留所有字段,因此使用(默认)JsonInclude.Value.ALWAYS
。
为了允许通过应用程序的Rest API进行部分更新,我还想区分特定设置为null
的值和保持不变的值。为此,Java8 Optional<?>
类似乎是正确的选择。
要获得适当的支持,我必须将Jdk8Module
添加到ObjectMapper
。一切都很简单。
开箱即用的反序列化行为正是我想要的。不存在的字段仍为默认字段(此处为null
),明确提供的null
值已反序列化为Optional.empty()
(或Optional.ofNullable(null)
)。
我想要的是Optional<?>
字段,其显式值null
将从生成的JSON中排除,但任何其他字段(例如普通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);
}
}
有很多方法可以(动态地)包含/排除字段及其值,但没有一种方法可以包含/排除#34;官方&#34;似乎可以做到这一点:
@JsonInclude
字段上的Optional<?>
注释实际上都是我想要的,但这太容易忘记和麻烦。MixIn
应该允许每种类型的JsonInclude
注释的全局定义,但显然不适用(按照上面的示例测试)。@JsonIgnore
(和相关的)注释是静态的,并不关心字段的价值。@JsonFilter
字段的每个类上设置Optional
,您需要知道PropertyFilter
中每个受影响的类型。即甚至不仅仅是在每个JsonInclude
字段上添加Optional
。@JsonView
不允许根据给定的bean实例的字段值动态包含/排除字段。JsonSerializer<?>
注册的自定义ObjectMapper.setNullValueSerializer()
仅在插入字段名称后调用,即如果我们不做任何操作,生成的JSON无效。BeanSerializerModifier
,但它无法访问字段的值。答案 0 :(得分:1)
编辑:根据StaxMan的回答,这似乎不是一个错误,而是一个功能,因为混合并不意味着为某种类型的每个属性添加注释。如问题中所述使用混合的尝试只会在
@JsonInclude
类上添加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
并将其设置在ObjectMapper
上。
只需包含自定义的introspector类并将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());
}
答案 1 :(得分:1)
到目前为止我能够进行调查,问题不在于混合,而在于@JsonInclude
的语义。对于类型的包含附件的含义有两种可能的解释:
这里的期望似乎是(1),但杰克逊实际上是出于历史原因(2)。这允许容易地违反给定POJO的所有属性,例如:
@JsonInclude(Include.NON_NULL)
public class Stuff {
public String name;
public Address address;
}
但不是,例如
@JsonInclude(Include.NON_NULL)
public class Address {
}
要过滤掉所有null
- 值Address
- 属性。