Встановлення значень за замовчуванням для нульових полів під час зіставлення з Джексоном


80

Я намагаюся зіставити деякі об'єкти JSON з об'єктами Java за допомогою Джексона. Деякі поля в об'єкті JSON є обов'язковими (які я можу позначити @NotNull), а деякі необов'язкові.

Після зіставлення з Джексоном усі поля, які не встановлені в об'єкті JSON, матимуть нульове значення в Java. Чи існує подібна анотація, @NotNullяка може сказати Джексону встановити значення за замовчуванням для учасника класу Java, якщо воно є нульовим?

Редагувати: щоб зробити питання більш зрозумілим, ось приклад коду.

Об'єкт Java:

class JavaObject {
    @NotNull
    public String notNullMember;

    @DefaultValue("Value")
    public String optionalMember;
}

Об'єкт JSON може бути:

{
    "notNullMember" : "notNull"
}

або:

{
    "notNullMember" : "notNull",
    "optionalMember" : "optional"
}

В @DefaultValueанотації просто щоб показати , що я прошу. Це не справжня анотація. Якщо об'єкт JSON схожий на перший приклад, я хочу, щоб значення optionalMemberбуло, "Value"а не null. Чи є анотація, яка робить таке?


Я шукаю подібне питання, але остаточне поле.
Даніель Яйцков,

Відповіді:


82

Немає приміток для встановлення значення за замовчуванням.
Ви можете встановити значення за замовчуванням лише на рівні класу Java:

public class JavaObject 
{
    public String notNullMember;

    public String optionalMember = "Value";
}

56
Це дає значення за замовчуванням, якщо json для optionalMember відсутній. Якщо json встановлює для нього значення "null", це буде значення null, а не "Value"
Lee Meador

1
Додана відповідь, яка показує, як економити Valueна випадокnull
Сергій Войтович

4
Крім того , якщо клас має AllArgsConstructor, значення за замовчуванням буде стерто з нульовими Див stackoverflow.com/q/48725922/9640261
NayoR

@LeeMeador, можливо, тому, що значення, в такому випадку, є "нульовим"?
pellyadolfo

32

Тільки один з пропонованих рішень тримає , default-valueколи some-value:nullбуло встановлено явно (POJO читаність втрачається там , і це незграбна)

Ось як можна тримати default-valueі ніколи не встановлювати йогоnull

@JsonProperty("some-value")
public String someValue = "default-value";

@JsonSetter("some-value")
public void setSomeValue(String s) {
    if (s != null) { 
        someValue = s; 
    }
}

1
Можливо, є спосіб це зробити для всіх полів класу?
Воланд

5

Ви можете створити власний JsonDeserializer і помітити це властивість за допомогою @JsonDeserialize(as = DefaultZero.class)

Наприклад: Щоб налаштувати BigDecimal за замовчуванням на НУЛЬ:

public static class DefaultZero extends JsonDeserializer<BigDecimal> {
    private final JsonDeserializer<BigDecimal> delegate;

    public DefaultZero(JsonDeserializer<BigDecimal> delegate) {
        this.delegate = delegate;
    }

    @Override
    public BigDecimal deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        return jsonParser.getDecimalValue();
    }

    @Override
    public BigDecimal getNullValue(DeserializationContext ctxt) throws JsonMappingException {
        return BigDecimal.ZERO;
    }
}

І використання:

class Sth {

   @JsonDeserialize(as = DefaultZero.class)
   BigDecimal property;
 }

4

Схоже, рішенням є встановлення значення властивостей всередині конструктора за замовчуванням. Отже, у цьому випадку клас Java:

class JavaObject {

    public JavaObject() {

        optionalMember = "Value";
    }

    @NotNull
    public String notNullMember;

    public String optionalMember;
}

Після зіставлення з Джексоном, якщо optionalMemberв JSON відсутнє, його значення в класі Java є "Value".

Однак мені все одно цікаво знати, чи існує рішення з анотаціями та без конструктора за замовчуванням.


6
Але якщо JSON має значення null, необов'язковий член закінчиться як null. Це може бути те, що хочеться чи ні.
Lee Meador

2
Це те саме, що просто встановити значення поля за замовчуванням. Не вирішує проблему.
Інокентій

2

Зробіть учасника приватним та додайте пару сетер / геттер. У вашому установчику, якщо значення нульове, замість цього встановіть значення за замовчуванням. Крім того, я показав, що фрагмент із геттером також повертає значення за замовчуванням, коли внутрішнє значення дорівнює нулю.

class JavaObject {
    private static final String DEFAULT="Default Value";

    public JavaObject() {
    }

    @NotNull
    private String notNullMember;
    public void setNotNullMember(String value){
            if (value==null) { notNullMember=DEFAULT; return; }
            notNullMember=value;
            return;
    }

    public String getNotNullMember(){
            if (notNullMember==null) { return DEFAULT;}
            return notNullMember;
    }

    public String optionalMember;
}

1
Сеттер встановлює ненульове значення, тому значення getter буде входити в оператор if кожного разу, коли його викликають тоді і тільки тоді, коли notNullMember ніколи не встановлено
NayoR

0

У мене була подібна проблема, але в моєму випадку значення за замовчуванням було в базі даних. Нижче наведено рішення для цього:

 @Configuration
 public class AppConfiguration {
 @Autowired
 private AppConfigDao appConfigDao;

 @Bean
 public Jackson2ObjectMapperBuilder builder() {
   Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
       .deserializerByType(SomeDto.class, 
 new SomeDtoJsonDeserializer(appConfigDao.findDefaultValue()));
   return builder;
 }

Потім SomeDtoJsonDeserializerвикористовується ObjectMapperдля десериалізації json та встановлення значення за замовчуванням, якщо ваше поле / об'єкт має значення null.


0

Вже є багато хороших пропозицій, але ось ще одна. Ви можете використовувати @JsonDeserialize для виконання довільного "дезінфікуючого засобу", який Джексон буде використовувати після десериалізації:

@JsonDeserialize(converter=Message1._Sanitizer.class)  
public class Message1 extends MessageBase
{
    public String string1 = "";
    public int integer1;

    public static class _Sanitizer extends StdConverter<Message1,Message1> {
        @Override
        public Message1 convert(Message1 message) {
            if (message.string1 == null) message.string1 = "";
            return message;
        }
    }
}

0

Інший варіант полягає у використанні InjectableValues і @JacksonInject . Це дуже корисно, якщо вам потрібно використовувати не завжди одне і те ж значення, але одне отримати з БД або десь ще для конкретного випадку. Ось приклад використання JacksonInject:

protected static class Some {
    private final String field1;
    private final String field2;

    public Some(@JsonProperty("field1") final String field1,
            @JsonProperty("field2") @JacksonInject(value = "defaultValueForField2",
                    useInput = OptBoolean.TRUE) final String field2) {
        this.field1 = requireNonNull(field1);
        this.field2 = requireNonNull(field2);
    }

    public String getField1() {
        return field1;
    }

    public String getField2() {
        return field2;
    }
}

@Test
public void testReadValueInjectables() throws JsonParseException, JsonMappingException, IOException {
    final ObjectMapper mapper = new ObjectMapper();
    final InjectableValues injectableValues =
            new InjectableValues.Std().addValue("defaultValueForField2", "somedefaultValue");
    mapper.setInjectableValues(injectableValues);

    final Some actualValueMissing = mapper.readValue("{\"field1\": \"field1value\"}", Some.class);
    assertEquals(actualValueMissing.getField1(), "field1value");
    assertEquals(actualValueMissing.getField2(), "somedefaultValue");

    final Some actualValuePresent =
            mapper.readValue("{\"field1\": \"field1value\", \"field2\": \"field2value\"}", Some.class);
    assertEquals(actualValuePresent.getField1(), "field1value");
    assertEquals(actualValuePresent.getField2(), "field2value");
}

Майте на увазі, що якщо ви використовуєте конструктор для створення сутності (це зазвичай трапляється, коли ви використовуєте @Value або @AllArgsConstructor в lombok ), і ви ставите @JacksonInjectне до конструктора, а до властивості, яка не буде працювати належним чином - значення введеного поле завжди буде перевизначати значення в JSON, незалежно від того , чи ставити useInput = OptBoolean.TRUEв @JacksonInject. Це тому, що jackson вводить ці властивості після виклику конструктора (навіть якщо властивість є final) - для поля встановлено правильне значення в конструкторі, але тоді воно замінено (перевірте: https://github.com/FasterXML/jackson-databind/ issues / 2678 та https://github.com/rzwitserloot/lombok/issues/1528#issuecomment-607725333для отримання додаткової інформації), цей тест, на жаль, проходить :

protected static class Some {
    private final String field1;

    @JacksonInject(value = "defaultValueForField2", useInput = OptBoolean.TRUE)
    private final String field2;

    public Some(@JsonProperty("field1") final String field1,
            @JsonProperty("field2") @JacksonInject(value = "defaultValueForField2",
                    useInput = OptBoolean.TRUE) final String field2) {
        this.field1 = requireNonNull(field1);
        this.field2 = requireNonNull(field2);
    }

    public String getField1() {
        return field1;
    }

    public String getField2() {
        return field2;
    }
}

@Test
public void testReadValueInjectablesIncorrectBehavior() throws JsonParseException, JsonMappingException, IOException {
    final ObjectMapper mapper = new ObjectMapper();
    final InjectableValues injectableValues =
            new InjectableValues.Std().addValue("defaultValueForField2", "somedefaultValue");
    mapper.setInjectableValues(injectableValues);

    final Some actualValueMissing = mapper.readValue("{\"field1\": \"field1value\"}", Some.class);
    assertEquals(actualValueMissing.getField1(), "field1value");
    assertEquals(actualValueMissing.getField2(), "somedefaultValue");

    final Some actualValuePresent =
            mapper.readValue("{\"field1\": \"field1value\", \"field2\": \"field2value\"}", Some.class);
    assertEquals(actualValuePresent.getField1(), "field1value");
    // unfortunately "field2value" is overrided because of putting "@JacksonInject" to the field
    assertEquals(actualValuePresent.getField2(), "somedefaultValue");
}

Сподіваюся, це допоможе комусь із подібною проблемою.

PS Я використовую jackson v. 2.9.6

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.