Bagaimana cara de / membuat serial objek yang tidak dapat diubah tanpa konstruktor default menggunakan ObjectMapper?

106

Saya ingin membuat serial dan deserialisasi objek yang tidak dapat diubah menggunakan com.fasterxml.jackson.databind.ObjectMapper.

Kelas tetap terlihat seperti ini (hanya 3 atribut internal, pengambil dan konstruktor):

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

Namun ketika saya menjalankan tes unit ini:

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

Saya mendapatkan pengecualian ini:

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

Pengecualian ini meminta saya untuk membuat konstruktor default, tetapi ini adalah objek yang tidak dapat diubah, jadi saya tidak ingin memilikinya. Bagaimana cara mengatur atribut internal? Ini benar-benar akan membingungkan pengguna API.

Jadi pertanyaan saya adalah: Dapatkah saya menghilangkan / membuat serial objek yang tidak dapat diubah tanpa konstruktor default?

Michal
sumber
Saat melakukan desrialisasi, desrializer tidak mengetahui tentang konstruktor Anda, sehingga mengenai konstruktor default. Oleh karena itu, Anda harus membuat konstruktor default, yang tidak akan mengubah kekekalan. Saya juga melihat kelas ini belum final, mengapa demikian? siapa pun dapat mengesampingkan fungsionalitasnya bukan?
Abhinaba Basu
Kelas belum final, karena saya lupa menulisnya di sana, terima kasih telah memperhatikan :)
Michal

Jawaban:

149

Untuk memberi tahu Jackson cara membuat objek untuk deserialisasi, gunakan anotasi @JsonCreatordan @JsonPropertyuntuk konstruktor Anda, seperti ini:

@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;
}
Sergei
sumber
1
+1 terima kasih Sergey. Itu berhasil .. namun saya mengalami masalah bahkan setelah menggunakan anotasi JsonCreator .. tetapi setelah beberapa tinjauan saya menyadari bahwa saya lupa menggunakan anotasi RequestBody di sumber daya saya. Sekarang berhasil .. Thanx sekali lagi.
Rahul
4
Bagaimana jika Anda tidak dapat menambahkan konstruktor default, atau anotasi @JsonCreatordan @JsonPropertykarena Anda tidak memiliki akses ke POJO untuk mengubahnya? Apakah masih ada cara untuk menghilangkan objek tersebut?
Jack Straw
1
@JackStraw 1) subclass dari objek dan menimpa semua getter dan setter. 2) tulis serializer / deserializer Anda sendiri untuk kelas tersebut dan gunakan (cari "custom serializer" 3) bungkus objek dan tulis serializer / deserializer hanya untuk satu properti (ini memungkinkan Anda melakukan lebih sedikit pekerjaan daripada menulis serializer untuk kelas itu sendiri, tapi mungkin tidak).
ErikE
Memberi +1 pada komentar Anda menyelamatkan saya! Setiap solusi sejauh ini yang saya temukan adalah membuat konstruktor default ...
Nilson Aguiar
34

Anda dapat menggunakan konstruktor default pribadi, Jackson kemudian akan mengisi bidang melalui refleksi bahkan jika final pribadi.

EDIT: Dan gunakan konstruktor default yang dilindungi / dilindungi paket untuk kelas induk jika Anda memiliki warisan.

tkruse
sumber
Hanya untuk membangun jawaban ini, jika kelas yang ingin Anda deserialisasi memperluas kelas lain, maka Anda dapat membuat konstruktor default dari kelas super (atau kedua kelas) dilindungi.
KWILLIAMS
3

Jawaban pertama Sergei Petunin benar. Namun, kita dapat menyederhanakan kode dengan menghapus anotasi @JsonProperty yang berlebihan pada setiap parameter konstruktor.

Itu bisa dilakukan dengan menambahkan com.fasterxml.jackson.module.paramnames.ParameterNamesModule ke ObjectMapper:

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

(Btw: modul ini terdaftar secara default di SpringBoot. Jika Anda menggunakan kacang ObjectMapper dari JacksonObjectMapperConfiguration atau jika Anda membuat ObjectMapper Anda sendiri dengan kacang Jackson2ObjectMapperBuilder maka Anda dapat melewati pendaftaran modul secara manual)

Sebagai contoh:

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

dan ObjectMapper melakukan deserialisasi json ini tanpa kesalahan:

{
  "field": "email",
  "error": "some text"
}
Vladyslav Diachenko
sumber