Ломбок 1.18.0 и Джексон 2.9.6 не работают вместе


Десериализация завершается ошибкой после обновления.

Я обновил свой микро-сервис с Spring 1.5.10.RELEASE до Spring 2.0.3.RELEASE, а также обновил lombok с 1.16.14 до 1.18.0 и jackson-datatype-jsr310 с 2.9.4 до 2.9.6.

Строка JSON -

{"heading":"Validation failed","detail":"field must not be null"}

Класс -

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class ErrorDetail {

   private final String heading;
   private final String detail;
   private String type;
}

Вызов метода -

ErrorDetail errorDetail = asObject(jsonString, ErrorDetail.class);

Метод, используемый для десериализации -

import com.fasterxml.jackson.databind.ObjectMapper;
// more imports and class defination.

private static <T> T asObject(final String str, Class<T> clazz) {
    try {
        return new ObjectMapper().readValue(str, clazz);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

Ошибка -

java.lang.RuntimeException: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.foo.bar.ErrorDetail` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (String)"{"heading":"Validation failed","detail":"field must not be null"}"; line: 1, column: 2]
3 3

3 ответа:

Ломбок перестал генерировать @ConstructorProperties на конструкторах с версией 1.16.20 (см. changelog ), потому что это могло бы сломать приложения Java 9+, которые используют модули. Эта аннотация содержит имена параметров конструктора (они удаляются при компиляции класса, так что это обходной путь, так что имена параметров все еще могут быть получены во время выполнения). Поскольку аннотации теперь не создаются по умолчанию, Джексон не может сопоставить имена полей конструктору параметры.

Решение 1: Используйте @NoArgsConstructor и @Setter, но вы потеряете неизменность (если это важно для вас).

обновление: Просто @NoArgsConstructor и @Getter (Без @Setter) также может работать (потому что INFER_PROPERTY_MUTATORS=true). Таким образом, вы можете сохранить класс неизменяемым, по крайней мере, из регулярного (неотражающего) кода.

Решение 2: Настройте lombok для повторного создания аннотаций, используя файл lombok.config , содержащий строку lombok.anyConstructor.addConstructorProperties = true. (Если это так используя модули, убедитесь, что java.desktop находится на пути к модулю.)

Решение 3: Используйте поддержку конструктора Джексона в сочетании с поддержкой Ломбока @Builder, как описано здесь.

Вы хотите десериализовать класс, который имеет конечное поле. поэтому вам нужно объявить конструктор, который содержит конечное поле для десериализации.

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class ErrorDetail {

private final String heading;
private final String detail;
private String type;

@JsonCreator
public ErrorDetail(@JsonProperty("heading") String heading, @JsonProperty("detail") String detail) {
    this.heading = heading;
    this.detail = detail;
}
}

А при десериализации с помощью mapper нужно MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS Установите это свойство false.

private static <T> T asObject(final String str, Class<T> clazz) {
    try {
        return new ObjectMapper().configure(MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS,false).readValue(str, clazz);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

Использование аннотации @Data, на мой взгляд, плохой подход. Пожалуйста, измените @Data на @Getting , @Setter, @EqualsAndHashcode и так далее ..

И напишите сюда, пожалуйста, если это поможет.

Обновить

Предлагаю, чтобы @Data создавали @RequiredArgsConstructor, а это конструктор с конечными полями, и без private String type;