При использовании Jackson в моем приложении Java для сериализации (POJO-JSON) и десериализации (JSON для POJO) Обычно я хочу сохранить все поля и, следовательно, использовать (по умолчанию) JsonInclude.Value.ALWAYS
.Jackson: отличается JsonInclude для всех свойств определенного типа (например, необязательно)
Для того чтобы разрешить частичные обновления с помощью 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);
}
}
Есть несколько способов (динамически) включить/исключить поля и их значения, но ни один из «официальных» способов, кажется, сделать трюк:
@JsonInclude
аннотации на каждом полеOptional<?>
действительно делают то, что я хочу, но это слишком легко забыть и громоздко.- Пользовательский
MixIn
должен учитывать глобальное определение аннотацииJsonInclude
для каждого типа, но, по-видимому, не применяется (как описано выше в примерах тестов). @JsonIgnore
(и связанные) аннотации являются статическими и не заботятся о значении поля.@JsonFilter
необходимо установить для каждого класса, содержащего поляOptional
, и вам нужно будет узнать каждый затронутый тип вPropertyFilter
. То есть даже стоит добавитьJsonInclude
на каждое полеOptional
.@JsonView
не допускают динамического включения/исключения полей на основе значения поля данного экземпляра компонента.- Пользовательский
JsonSerializer<?>
, зарегистрированный черезObjectMapper.setNullValueSerializer()
, вызывается только после того, как имя поля было вставлено, то есть сгенерированный JSON недействителен, если мы просто ничего не делаем. - Применяется таможня
BeanSerializerModifier
, прежде чем имя поля будет вставлено в JSON, но у него нет доступа к значению поля.
Записан это как [выпуск # 1522 для jackson-databind] (https://github.com/FasterXML/jackson-databind/issues/1522). Посмотрим, можно ли там легко установить. – Carsten