Как де / сериализовать неизменяемый объект без конструктора по умолчанию с помощью ObjectMapper?


106

Я хочу сериализовать и десериализовать неизменяемый объект с помощью com.fasterxml.jackson.databind.ObjectMapper.

Неизменяемый класс выглядит так (всего 3 внутренних атрибута, геттеры и конструкторы):

public final class ImportResultItemImpl implements ImportResultItem {

    private final ImportResultItemType resultType;

    private final String message;

    private final String name;

    public ImportResultItemImpl(String name, ImportResultItemType resultType, String message) {
        super();
        this.resultType = resultType;
        this.message = message;
        this.name = name;
    }

    public ImportResultItemImpl(String name, ImportResultItemType resultType) {
        super();
        this.resultType = resultType;
        this.name = name;
        this.message = null;
    }

    @Override
    public ImportResultItemType getResultType() {
        return this.resultType;
    }

    @Override
    public String getMessage() {
        return this.message;
    }

    @Override
    public String getName() {
        return this.name;
    }
}

Однако, когда я запускаю этот модульный тест:

@Test
public void testObjectMapper() throws Exception {
    ImportResultItemImpl originalItem = new ImportResultItemImpl("Name1", ImportResultItemType.SUCCESS);
    String serialized = new ObjectMapper().writeValueAsString((ImportResultItemImpl) originalItem);
    System.out.println("serialized: " + serialized);

    //this line will throw exception
    ImportResultItemImpl deserialized = new ObjectMapper().readValue(serialized, ImportResultItemImpl.class);
}

Я получаю это исключение:

com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class eu.ibacz.pdkd.core.service.importcommon.ImportResultItemImpl]: can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)
 at [Source: {"resultType":"SUCCESS","message":null,"name":"Name1"}; line: 1, column: 2]
    at 
... nothing interesting here

Это исключение просит меня создать конструктор по умолчанию, но это неизменяемый объект, поэтому я не хочу его иметь. Как бы он установил внутренние атрибуты? Это полностью запутает пользователя API.

Итак, мой вопрос: могу ли я каким-то образом де / сериализовать неизменяемые объекты без конструктора по умолчанию?


Во время десериализации десериализатор не знает ни о одном из ваших конструкторов, поэтому он попадает в конструктор по умолчанию. В связи с этим вам необходимо создать конструктор по умолчанию, который не изменит неизменяемость. Также я вижу, что класс не окончательный, почему так? кто-нибудь может переопределить функциональность, не так ли?
Абхинаба Басу,

Класс не финальный, потому что я забыл написать его там, спасибо, что заметили :)
Михал

Ответы:


149

Для того, чтобы Джексон знает , как создать объект для десериализации, используйте @JsonCreatorи @JsonPropertyаннотации для конструкторов, как это:

@JsonCreator
public ImportResultItemImpl(@JsonProperty("name") String name, 
        @JsonProperty("resultType") ImportResultItemType resultType, 
        @JsonProperty("message") String message) {
    super();
    this.resultType = resultType;
    this.message = message;
    this.name = name;
}

1
+1 спасибо Сергею. Это сработало ... однако у меня были проблемы даже после использования аннотации JsonCreator ... но после некоторого обзора я понял, что забыл использовать аннотацию RequestBody в моем ресурсе. Теперь это работает .. Еще раз спасибо.
Rahul

4
Что делать , если вы не можете добавить конструктор по умолчанию, или @JsonCreatorи @JsonPropertyаннотаций , потому что у вас нет доступа к POJO , чтобы изменить его? Есть ли еще способ десериализации объекта?
Джек Стро

1
@JackStraw 1) подкласс объекта и переопределить все геттеры и сеттеры. 2) напишите свой собственный сериализатор / десериализатор для класса и используйте его (поиск «пользовательского сериализатора» 3) оберните объект и напишите сериализатор / десериализатор только для одного свойства (это может позволить вам сделать меньше работы, чем написание сериализатора для сам класс, а может и нет).
ErikE

+1 ваш комментарий спас меня! До сих пор каждое решение, которое я находил, создавало конструктор по умолчанию ...
Нильсон Агияр,

34

Вы можете использовать частный конструктор по умолчанию, затем Джексон заполнит поля через отражение, даже если они являются закрытыми окончательными.

РЕДАКТИРОВАТЬ: И используйте защищенный / защищенный пакетом конструктор по умолчанию для родительских классов, если у вас есть наследование.


Чтобы основываться на этом ответе, если класс, который вы хотите десериализовать, расширяет другой класс, вы можете сделать конструктор по умолчанию суперкласса (или обоих классов) защищенным.
КУИЛЬЯМС

4

Первый ответ Сергея Петунина правильный. Однако мы могли бы упростить код, удалив избыточные аннотации @JsonProperty для каждого параметра конструктора.

Это можно сделать, добавив com.fasterxml.jackson.module.paramnames.ParameterNamesModule в ObjectMapper:

new ObjectMapper()
        .registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES))

(Кстати: этот модуль по умолчанию зарегистрирован в SpringBoot. Если вы используете bean-компонент ObjectMapper из JacksonObjectMapperConfiguration или если вы создаете свой собственный ObjectMapper с bean-компонентом Jackson2ObjectMapperBuilder, вы можете пропустить ручную регистрацию модуля)

Например:

public class FieldValidationError {
  private final String field;
  private final String error;

  @JsonCreator
  public FieldValidationError(String field,
                              String error) {
    this.field = field;
    this.error = error;
  }

  public String getField() {
    return field;
  }

  public String getError() {
    return error;
  }
}

и ObjectMapper десериализует этот json без ошибок:

{
  "field": "email",
  "error": "some text"
}

Отличный ответ, хорошо работает
Али
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.