Як де / серіалізувати незмінний об'єкт без конструктора за замовчуванням за допомогою 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.

Отже, моє запитання таке: чи можу я якось де / серіалізувати незмінні об'єкти без конструктора за замовчуванням?


Під час дезіалізації дезіалізатор не знає жодного з ваших конструкторів, тому він потрапляє до конструктора за замовчуванням. Завдяки цьому вам потрібно створити конструктор за замовчуванням, що не змінить незмінність. Також я бачу, що клас не є остаточним, чому так? хтось може замінити функціональність, чи не так?
Абхінаба Басу

Клас не є заключним, тому що я забув його написати там, дякую за те, що помітили :)
Michal

Відповіді:


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 на своєму ресурсі. Зараз це працює .. Дякуємо ще раз.
Рахул

4
Що робити , якщо ви не можете додати конструктор за замовчуванням, або @JsonCreatorі @JsonPropertyанотацій , бо у вас немає доступу до POJO , щоб змінити його? Чи є ще спосіб десеріалізації об'єкта?
Джек Солома

1
@JackStraw 1) підклас від об'єкта і переосмислює всі геттери та сеттери. 2) напишіть власний серіалізатор / десеріалізатор для класу та використовуйте його (пошукайте "спеціальний серіалізатор" 3), оберніть об'єкт і напишіть серіалізатор / десеріалізатор лише для одного властивості (це може дати вам менше роботи, ніж написання серіалізатора для сам клас, а може й ні).
ЕрікЕ

+1 ваш коментар врятував мене! Кожне рішення досі, що я знаходив, - це створити конструктор за замовчуванням ...
Нілсон Агіяр

34

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

EDIT: І використовуйте захищений / захищений пакетом конструктор для батьківських класів, якщо у вас є спадщина.


Просто для розробки цієї відповіді, якщо клас, який ви хочете десаріалізувати, розширює інший клас, то ви можете зробити конструктор за замовчуванням суперкласу (або обох класів) захищеним.
KWILLIAMS

3

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

Це можна зробити, додавши com.fasterxml.jackson.module.paramnames.ParameterNamesModule в ObjectMapper:

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

(Btw: цей модуль зареєстрований за замовчуванням у SpringBoot. Якщо ви використовуєте ObjectMapper bean від 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.