2017-01-23 3 views
1

При использовании 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); 
    } 
} 

Есть несколько способов (динамически) включить/исключить поля и их значения, но ни один из «официальных» способов, кажется, сделать трюк:

  1. @JsonInclude аннотации на каждом поле Optional<?> действительно делают то, что я хочу, но это слишком легко забыть и громоздко.
  2. Пользовательский MixIn должен учитывать глобальное определение аннотации JsonInclude для каждого типа, но, по-видимому, не применяется (как описано выше в примерах тестов).
  3. @JsonIgnore (и связанные) аннотации являются статическими и не заботятся о значении поля.
  4. @JsonFilter необходимо установить для каждого класса, содержащего поля Optional, и вам нужно будет узнать каждый затронутый тип в PropertyFilter. То есть даже стоит добавить JsonInclude на каждое поле Optional.
  5. @JsonView не допускают динамического включения/исключения полей на основе значения поля данного экземпляра компонента.
  6. Пользовательский JsonSerializer<?>, зарегистрированный через ObjectMapper.setNullValueSerializer(), вызывается только после того, как имя поля было вставлено, то есть сгенерированный JSON недействителен, если мы просто ничего не делаем.
  7. Применяется таможня BeanSerializerModifier, прежде чем имя поля будет вставлено в JSON, но у него нет доступа к значению поля.

ответ

1
Edit:

Согласно ответу StaxMan, это, кажется, не будет ошибкой, но функция, так как микс модули не предназначены для добавления аннотаций для каждого свойства определенного типа. Попытка использования mix-ins, как описано в вопросе, просто добавляет аннотацию @JsonInclude к классу Optional, который имеет другое значение (уже описанное в этом другом ответе).


рабочего раствора этого заключается в том, чтобы переопределить 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()); 
} 

Все брошено вместе будет выглядеть следующим образом:


public class OptionalObjectMappingTest { 
    public static class MyBean { 
     public Integer one; 
     public Optional<Integer> two; 
     public Optional<Integer> three; 
     public Optional<Integer> four; 
    } 

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

    @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}"; 
     // SUCCESS 
     Assert.assertEquals(expected, result); 
    } 
} 
+0

Записан это как [выпуск # 1522 для jackson-databind] (https://github.com/FasterXML/jackson-databind/issues/1522). Посмотрим, можно ли там легко установить. – Carsten

1

Насколько я смог исследовать, проблема не в том, что вы смешиваете, но семантика @JsonInclude. Есть 2 возможных интерпретации смысла привязанности включения типа:

  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 -значная Address -свойствами быть отфильтрованы.

+0

Хорошо, что я прояснил свое недоразумение. Теперь мне нужен только «правильный» способ достижения моей цели :) – Carsten

Смежные вопросы