Lebih Banyak Anotasi Jackson

1. Ikhtisar

Artikel ini mencakup beberapa anotasi tambahan yang tidak tercakup dalam artikel sebelumnya, Panduan Anotasi Jackson - kita akan membahas tujuh di antaranya.

2. @JsonIentityReference

@JsonIdentityReference digunakan untuk penyesuaian referensi ke objek yang akan diserialkan sebagai identitas objek, bukan POJO lengkap. Ia bekerja sama dengan @JsonIdentityInfo untuk memaksa penggunaan identitas objek di setiap serialisasi, berbeda dari all-but-the-first-time ketika @JsonIdentityReference tidak ada. Beberapa anotasi ini paling membantu saat menangani dependensi melingkar di antara objek. Silakan merujuk ke bagian 4 artikel Jackson - Bidirectional Relationship untuk informasi lebih lanjut.

Untuk mendemonstrasikan penggunaan @JsonIdentityReference , kita akan mendefinisikan dua kelas kacang yang berbeda, tanpa dan dengan anotasi ini.

Kacang tanpa @JsonIdentityReference :

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") public class BeanWithoutIdentityReference { private int id; private String name; // constructor, getters and setters }

Untuk kacang menggunakan @JsonIdentityReference , kami memilih properti id sebagai identitas objek:

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") @JsonIdentityReference(alwaysAsId = true) public class BeanWithIdentityReference { private int id; private String name; // constructor, getters and setters }

Dalam kasus pertama, di mana @JsonIdentityReference tidak ada, kacang tersebut diserialkan dengan detail lengkap tentang propertinya:

BeanWithoutIdentityReference bean = new BeanWithoutIdentityReference(1, "Bean Without Identity Reference Annotation"); String jsonString = mapper.writeValueAsString(bean);

Output dari serialisasi diatas:

{ "id": 1, "name": "Bean Without Identity Reference Annotation" }

Ketika @JsonIdentityReference digunakan, bean diserialkan sebagai identitas sederhana:

BeanWithIdentityReference bean = new BeanWithIdentityReference(1, "Bean With Identity Reference Annotation"); String jsonString = mapper.writeValueAsString(bean); assertEquals("1", jsonString);

3. @JsonAppend

The @JsonAppend penjelasan digunakan untuk menambahkan properti virtual untuk sebuah objek selain yang biasa ketika objek yang serial. Ini diperlukan saat kita ingin menambahkan informasi tambahan langsung ke dalam string JSON, daripada mengubah definisi kelas. Misalnya, mungkin lebih mudah untuk memasukkan metadata versi kacang ke dokumen JSON terkait daripada menyediakannya dengan properti tambahan.

Asumsikan kita memiliki kacang tanpa @JsonAppend sebagai berikut:

public class BeanWithoutAppend { private int id; private String name; // constructor, getters and setters }

Sebuah pengujian akan mengkonfirmasi bahwa jika anotasi @JsonAppend tidak ada , keluaran serialisasi tidak berisi informasi tentang properti versi tambahan , terlepas dari kenyataan bahwa kami mencoba untuk menambahkan ke objek ObjectWriter :

BeanWithoutAppend bean = new BeanWithoutAppend(2, "Bean Without Append Annotation"); ObjectWriter writer = mapper.writerFor(BeanWithoutAppend.class).withAttribute("version", "1.0"); String jsonString = writer.writeValueAsString(bean);

Output serialisasi:

{ "id": 2, "name": "Bean Without Append Annotation" }

Sekarang, katakanlah kita memiliki kacang yang dianotasi dengan @JsonAppend :

@JsonAppend(attrs = { @JsonAppend.Attr(value = "version") }) public class BeanWithAppend { private int id; private String name; // constructor, getters and setters }

Pengujian serupa dengan yang sebelumnya akan memverifikasi bahwa ketika anotasi @JsonAppend diterapkan, properti tambahan disertakan setelah serialisasi:

BeanWithAppend bean = new BeanWithAppend(2, "Bean With Append Annotation"); ObjectWriter writer = mapper.writerFor(BeanWithAppend.class).withAttribute("version", "1.0"); String jsonString = writer.writeValueAsString(bean);

Output dari serialisasi tersebut menunjukkan bahwa properti versi telah ditambahkan:

{ "id": 2, "name": "Bean With Append Annotation", "version": "1.0" }

4. @JsonNaming

The @JsonNaming penjelasan digunakan untuk memilih strategi penamaan untuk properti di serialisasi, override default. Dengan menggunakan elemen nilai , kita dapat menentukan strategi apa pun, termasuk strategi khusus.

Selain default, yaitu LOWER_CAMEL_CASE (mis. LowerCamelCase ), perpustakaan Jackson memberi kita empat strategi penamaan properti bawaan lainnya untuk kenyamanan:

  • KEBAB_CASE : Elemen nama dipisahkan dengan tanda hubung, misalnya kebab-case .
  • LOWER_CASE : Semua huruf kecil tanpa pemisah, misalnya huruf kecil .
  • SNAKE_CASE : Semua huruf adalah huruf kecil dengan garis bawah sebagai pemisah antara elemen nama, misalnya snake_case .
  • UPPER_CAMEL_CASE : Semua elemen nama, termasuk yang pertama, dimulai dengan huruf kapital, diikuti dengan huruf kecil dan tidak ada pemisah, misalnya UpperCamelCase .

Contoh ini akan mengilustrasikan cara untuk membuat serial properti menggunakan nama kasus ular, di mana properti bernama beanName diserialkan sebagai bean_name.

Diberikan definisi kacang:

@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) public class NamingBean { private int id; private String beanName; // constructor, getters and setters }

Pengujian di bawah ini menunjukkan bahwa aturan penamaan yang ditentukan berfungsi seperti yang diperlukan:

NamingBean bean = new NamingBean(3, "Naming Bean"); String jsonString = mapper.writeValueAsString(bean); assertThat(jsonString, containsString("bean_name"));

The jsonString variabel berisi data sebagai berikut:

{ "id": 3, "bean_name": "Naming Bean" }

5. @JsonProtyDescription

Pustaka Jackson dapat membuat skema JSON untuk tipe Java dengan bantuan modul terpisah yang disebut JSON Schema. Skema ini berguna ketika kita ingin menentukan keluaran yang diharapkan saat membuat serial objek Java, atau untuk memvalidasi dokumen JSON sebelum deserialisasi.

The @JsonPropertyDescription penjelasan memungkinkan deskripsi dibaca manusia untuk ditambahkan ke JSON skema diciptakan dengan menyediakan deskripsi lapangan.

Bagian ini menggunakan kacang yang dideklarasikan di bawah ini untuk mendemonstrasikan kapabilitas @JsonPropertyDescription :

public class PropertyDescriptionBean { private int id; @JsonPropertyDescription("This is a description of the name property") private String name; // getters and setters }

Metode untuk membuat skema JSON dengan penambahan kolom deskripsi ditampilkan di bawah ini:

SchemaFactoryWrapper wrapper = new SchemaFactoryWrapper(); mapper.acceptJsonFormatVisitor(PropertyDescriptionBean.class, wrapper); JsonSchema jsonSchema = wrapper.finalSchema(); String jsonString = mapper.writeValueAsString(jsonSchema); assertThat(jsonString, containsString("This is a description of the name property"));

Seperti yang bisa kita lihat, pembuatan skema JSON berhasil:

{ "type": "object", "id": "urn:jsonschema:com:baeldung:jackson:annotation:extra:PropertyDescriptionBean", "properties": { "name": { "type": "string", "description": "This is a description of the name property" }, "id": { "type": "integer" } } }

6. @JsonPOJOBuilder

The @JsonPOJOBuilder penjelasan digunakan untuk mengkonfigurasi kelas pembangun untuk menyesuaikan deserialization dokumen JSON untuk memulihkan POJOs ketika konvensi penamaan berbeda dari default.

Misalkan kita perlu melakukan deserialisasi string JSON berikut:

{ "id": 5, "name": "POJO Builder Bean" }

Sumber JSON tersebut akan digunakan untuk membuat instance POJOBuilderBean :

@JsonDeserialize(builder = BeanBuilder.class) public class POJOBuilderBean { private int identity; private String beanName; // constructor, getters and setters }

Nama properti kacang berbeda dari nama bidang dalam string JSON. Di sinilah @JsonPOJOBuilder datang untuk menyelamatkan.

The @JsonPOJOBuilder penjelasan disertai dengan dua sifat:

  • buildMethodName: The name of the no-arg method used to instantiate the expected bean after binding JSON fields to that bean's properties. The default name is build.
  • withPrefix: The name prefix for auto-detection of matching between the JSON and bean's properties. The default prefix is with.

This example makes use of the BeanBuilder class below, which is used on POJOBuilderBean:

@JsonPOJOBuilder(buildMethodName = "createBean", withPrefix = "construct") public class BeanBuilder { private int idValue; private String nameValue; public BeanBuilder constructId(int id) { idValue = id; return this; } public BeanBuilder constructName(String name) { nameValue = name; return this; } public POJOBuilderBean createBean() { return new POJOBuilderBean(idValue, nameValue); } }

In the code above, we have configured the @JsonPOJOBuilder to use a build method called createBean and the construct prefix for matching properties.

The application of @JsonPOJOBuilder to a bean is described and tested as follows:

String jsonString = "{\"id\":5,\"name\":\"POJO Builder Bean\"}"; POJOBuilderBean bean = mapper.readValue(jsonString, POJOBuilderBean.class); assertEquals(5, bean.getIdentity()); assertEquals("POJO Builder Bean", bean.getBeanName());

The result shows that a new data object has been successfully re-created from a JSON source in despite a mismatch in properties' names.

7. @JsonTypeId

The @JsonTypeId annotation is used to indicate that the annotated property should be serialized as the type id when including polymorphic type information, rather than as a regular property. That polymorphic metadata is used during deserialization to recreate objects of the same subtypes as they were before serialization, rather than of the declared supertypes.

For more information on Jackson's handling of inheritance, see section 2 of the Inheritance in Jackson.

Let's say we have a bean class definition as follows:

public class TypeIdBean { private int id; @JsonTypeId private String name; // constructor, getters and setters }

The following test validates that @JsonTypeId works as it is meant to:

mapper.enableDefaultTyping(DefaultTyping.NON_FINAL); TypeIdBean bean = new TypeIdBean(6, "Type Id Bean"); String jsonString = mapper.writeValueAsString(bean); assertThat(jsonString, containsString("Type Id Bean"));

The serialization process' output:

[ "Type Id Bean", { "id": 6 } ]

8. @JsonTypeIdResolver

The @JsonTypeIdResolver annotation is used to signify a custom type identity handler in serialization and deserialization. That handler is responsible for conversion between Java types and type id included in a JSON document.

Suppose that we want to embed type information in a JSON string when dealing with the following class hierarchy.

The AbstractBean superclass:

@JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@type" ) @JsonTypeIdResolver(BeanIdResolver.class) public class AbstractBean { private int id; protected AbstractBean(int id) { this.id = id; } // no-arg constructor, getter and setter }

The FirstBean subclass:

public class FirstBean extends AbstractBean { String firstName; public FirstBean(int id, String name) { super(id); setFirstName(name); } // no-arg constructor, getter and setter }

The LastBean subclass:

public class LastBean extends AbstractBean { String lastName; public LastBean(int id, String name) { super(id); setLastName(name); } // no-arg constructor, getter and setter }

Instances of those classes are used to populate a BeanContainer object:

public class BeanContainer { private List beans; // getter and setter }

We can see that the AbstractBean class is annotated with @JsonTypeIdResolver, indicating that it uses a custom TypeIdResolver to decide how to include subtype information in serialization and how to make use of that metadata the other way round.

Here is the resolver class to handle inclusion of type information:

public class BeanIdResolver extends TypeIdResolverBase { private JavaType superType; @Override public void init(JavaType baseType) { superType = baseType; } @Override public Id getMechanism() { return Id.NAME; } @Override public String idFromValue(Object obj) { return idFromValueAndType(obj, obj.getClass()); } @Override public String idFromValueAndType(Object obj, Class subType) { String typeId = null; switch (subType.getSimpleName()) { case "FirstBean": typeId = "bean1"; break; case "LastBean": typeId = "bean2"; } return typeId; } @Override public JavaType typeFromId(DatabindContext context, String id) { Class subType = null; switch (id) { case "bean1": subType = FirstBean.class; break; case "bean2": subType = LastBean.class; } return context.constructSpecializedType(superType, subType); } }

The two most notable methods are idFromValueAndType and typeFromId, with the former telling the way to include type information when serializing POJOs and the latter determining the subtypes of re-created objects using that metadata.

In order to make sure that both serialization and deserialization work well, let's write a test to validate the complete progress.

Pertama, kita perlu membuat wadah kacang dan kelas kacang, lalu mengisi wadah itu dengan instance kacang:

FirstBean bean1 = new FirstBean(1, "Bean 1"); LastBean bean2 = new LastBean(2, "Bean 2"); List beans = new ArrayList(); beans.add(bean1); beans.add(bean2); BeanContainer serializedContainer = new BeanContainer(); serializedContainer.setBeans(beans);

Selanjutnya, objek BeanContainer diserialkan dan kami mengonfirmasi bahwa string yang dihasilkan berisi informasi jenis:

String jsonString = mapper.writeValueAsString(serializedContainer); assertThat(jsonString, containsString("bean1")); assertThat(jsonString, containsString("bean2"));

Output dari serialisasi ditunjukkan di bawah ini:

{ "beans": [ { "@type": "bean1", "id": 1, "firstName": "Bean 1" }, { "@type": "bean2", "id": 2, "lastName": "Bean 2" } ] }

Struktur JSON tersebut akan digunakan untuk membuat ulang objek dari subtipe yang sama seperti sebelum serialisasi. Berikut langkah-langkah implementasi deserialisasi:

BeanContainer deserializedContainer = mapper.readValue(jsonString, BeanContainer.class); List beanList = deserializedContainer.getBeans(); assertThat(beanList.get(0), instanceOf(FirstBean.class)); assertThat(beanList.get(1), instanceOf(LastBean.class));

9. Kesimpulan

Tutorial ini menjelaskan beberapa anotasi Jackson yang kurang umum secara mendetail. Penerapan contoh dan cuplikan kode ini dapat ditemukan di proyek GitHub.