Mengurai YAML dengan SnakeYAML

1. Ikhtisar

Dalam tutorial ini, kita akan belajar bagaimana menggunakan perpustakaan SnakeYAML untuk membuat serial objek Java ke dokumen YAML dan sebaliknya .

2. Pengaturan Proyek

Untuk menggunakan SnakeYAML dalam proyek kami, kami akan menambahkan ketergantungan Maven berikut (versi terbaru dapat ditemukan di sini):

 org.yaml snakeyaml 1.21 

3. Titik Masuk

Kelas Yaml adalah titik masuk untuk API:

Yaml yaml = new Yaml();

Karena implementasinya tidak aman untuk utas, utas yang berbeda harus memiliki contoh Yaml sendiri .

4. Memuat Dokumen YAML

Pustaka menyediakan dukungan untuk memuat dokumen dari String atau InputStream . Mayoritas contoh kode di sini akan didasarkan pada penguraian InputStream .

Mari kita mulai dengan mendefinisikan dokumen YAML sederhana, dan menamai file tersebut sebagai customer.yaml :

firstName: "John" lastName: "Doe" age: 20

4.1. Penggunaan Dasar

Sekarang kita akan mengurai dokumen YAML di atas dengan kelas Yaml :

Yaml yaml = new Yaml(); InputStream inputStream = this.getClass() .getClassLoader() .getResourceAsStream("customer.yaml"); Map obj = yaml.load(inputStream); System.out.println(obj);

Kode di atas menghasilkan output berikut:

{firstName=John, lastName=Doe, age=20}

Secara default, metode load () mengembalikan instance Map . Membuat kueri objek Map setiap kali akan mengharuskan kita mengetahui nama kunci properti terlebih dahulu, dan juga tidak mudah untuk melintasi properti bertingkat.

4.2. Jenis Kustom

Pustaka juga menyediakan cara untuk memuat dokumen sebagai kelas kustom . Opsi ini akan memudahkan traversal data dalam memori.

Mari tentukan kelas Pelanggan dan coba muat dokumen lagi:

public class Customer { private String firstName; private String lastName; private int age; // getters and setters }

Dengan asumsi dokumen YAML dideserialisasi sebagai tipe yang diketahui, kita dapat menentukan tag global eksplisit dalam dokumen.

Mari perbarui dokumen dan simpan dalam file baru customer_with_type.yaml:

!!com.baeldung.snakeyaml.Customer firstName: "John" lastName: "Doe" age: 20

Perhatikan baris pertama dalam dokumen, yang menyimpan info tentang kelas yang akan digunakan saat memuatnya.

Sekarang kami akan memperbarui kode yang digunakan di atas, dan meneruskan nama file baru sebagai masukan:

Yaml yaml = new Yaml(); InputStream inputStream = this.getClass() .getClassLoader() .getResourceAsStream("yaml/customer_with_type.yaml"); Customer customer = yaml.load(inputStream); 

Metode load () sekarang mengembalikan instance tipe Pelanggan . Kelemahan dari pendekatan ini adalah tipe harus diekspor sebagai pustaka agar dapat digunakan jika diperlukan .

Meskipun, kita dapat menggunakan tag lokal eksplisit yang tidak perlu mengekspor pustaka.

Cara lain untuk memuat tipe kustom adalah dengan menggunakan kelas Constructor . Dengan cara ini kita dapat menentukan tipe root untuk dokumen YAML yang akan diurai. Mari kita buat instance Constructor dengan tipe Pelanggan sebagai tipe root dan meneruskannya ke instance Yaml .

Sekarang saat memuat customer.yaml, kita akan mendapatkan objek Customer :

Yaml yaml = new Yaml(new Constructor(Customer.class));

4.3. Jenis Implisit

Jika tidak ada tipe yang ditentukan untuk properti tertentu, pustaka secara otomatis mengonversi nilai menjadi tipe implisit .

Sebagai contoh:

1.0 -> Float 42 -> Integer 2009-03-30 -> Date

Mari kita uji jenis konversi implisit ini menggunakan kasus uji:

@Test public void whenLoadYAML_thenLoadCorrectImplicitTypes() { Yaml yaml = new Yaml(); Map document = yaml.load("3.0: 2018-07-22"); assertNotNull(document); assertEquals(1, document.size()); assertTrue(document.containsKey(3.0d)); }

4.4. Objek dan Koleksi Bersarang

Dengan tipe level teratas, pustaka secara otomatis mendeteksi tipe objek bertingkat , kecuali itu adalah antarmuka atau kelas abstrak, dan deserialisasi dokumen ke dalam tipe bertingkat yang relevan.

Mari tambahkan detail Kontak dan Alamat ke customer.yaml, dan simpan file baru sebagai customer_with_contact_details_and_address.yaml.

Sekarang kami akan mengurai dokumen YAML baru:

firstName: "John" lastName: "Doe" age: 31 contactDetails: - type: "mobile" number: 123456789 - type: "landline" number: 456786868 homeAddress: line: "Xyz, DEF Street" city: "City Y" state: "State Y" zip: 345657 

Kelas pelanggan juga harus mencerminkan perubahan ini. Inilah kelas yang diperbarui:

public class Customer { private String firstName; private String lastName; private int age; private List contactDetails; private Address homeAddress; // getters and setters } 

Mari kita lihat bagaimana kelas Kontak dan Alamat terlihat:

public class Contact { private String type; private int number; // getters and setters }
public class Address { private String line; private String city; private String state; private Integer zip; // getters and setters }

Sekarang kita akan menguji Yaml # load () dengan kasus uji yang diberikan:

@Test public void whenLoadYAMLDocumentWithTopLevelClass_thenLoadCorrectJavaObjectWithNestedObjects() { Yaml yaml = new Yaml(new Constructor(Customer.class)); InputStream inputStream = this.getClass() .getClassLoader() .getResourceAsStream("yaml/customer_with_contact_details_and_address.yaml"); Customer customer = yaml.load(inputStream); assertNotNull(customer); assertEquals("John", customer.getFirstName()); assertEquals("Doe", customer.getLastName()); assertEquals(31, customer.getAge()); assertNotNull(customer.getContactDetails()); assertEquals(2, customer.getContactDetails().size()); assertEquals("mobile", customer.getContactDetails() .get(0) .getType()); assertEquals(123456789, customer.getContactDetails() .get(0) .getNumber()); assertEquals("landline", customer.getContactDetails() .get(1) .getType()); assertEquals(456786868, customer.getContactDetails() .get(1) .getNumber()); assertNotNull(customer.getHomeAddress()); assertEquals("Xyz, DEF Street", customer.getHomeAddress() .getLine()); }

4.5. Koleksi yang Aman Jenis

Jika satu atau beberapa properti dari kelas Java yang diberikan adalah kumpulan tipe-aman (generik), maka penting untuk menentukan TypeDescription sehingga tipe berparameter yang benar diidentifikasi.

Mari kita ambil satu Pelanggan yang memiliki lebih dari satu Kontak , dan coba muat:

firstName: "John" lastName: "Doe" age: 31 contactDetails: - { type: "mobile", number: 123456789} - { type: "landline", number: 123456789}

Untuk memuat dokumen ini, kita dapat menentukan TypeDescription untuk properti yang diberikan di kelas tingkat atas :

Constructor constructor = new Constructor(Customer.class); TypeDescription customTypeDescription = new TypeDescription(Customer.class); customTypeDescription.addPropertyParameters("contactDetails", Contact.class); constructor.addTypeDescription(customTypeDescription); Yaml yaml = new Yaml(constructor);

4.6. Memuat Banyak Dokumen

Mungkin ada kasus di mana, dalam satu File terdapat beberapa dokumen YAML, dan kami ingin mengurai semuanya. Kelas Yaml menyediakan metode loadAll () untuk melakukan tipe penguraian seperti itu.

By default, the method returns an instance of Iterable where each object is of type Map. If a custom type is desired then we can use the Constructor instance as discussed above.

Consider the following documents in a single file:

--- firstName: "John" lastName: "Doe" age: 20 --- firstName: "Jack" lastName: "Jones" age: 25

We can parse the above using the loadAll() method as shown in the below code sample:

@Test public void whenLoadMultipleYAMLDocuments_thenLoadCorrectJavaObjects() { Yaml yaml = new Yaml(new Constructor(Customer.class)); InputStream inputStream = this.getClass() .getClassLoader() .getResourceAsStream("yaml/customers.yaml"); int count = 0; for (Object object : yaml.loadAll(inputStream)) { count++; assertTrue(object instanceof Customer); } assertEquals(2,count); }

5. Dumping YAML Documents

The library also provides a method to dump a given Java object into a YAML document. The output could be a String or a specified file/stream.

5.1. Basic Usage

We'll start with a simple example of dumping an instance of Map to a YAML document (String):

@Test public void whenDumpMap_thenGenerateCorrectYAML() { Map data = new LinkedHashMap(); data.put("name", "Silenthand Olleander"); data.put("race", "Human"); data.put("traits", new String[] { "ONE_HAND", "ONE_EYE" }); Yaml yaml = new Yaml(); StringWriter writer = new StringWriter(); yaml.dump(data, writer); String expectedYaml = "name: Silenthand Olleander\nrace: Human\ntraits: [ONE_HAND, ONE_EYE]\n"; assertEquals(expectedYaml, writer.toString()); }

The above code produces the following output (note that using an instance of LinkedHashMap preserves the order of the output data):

name: Silenthand Olleander race: Human traits: [ONE_HAND, ONE_EYE]

5.2. Custom Java Objects

We can also choose to dump custom Java types into an output stream. This will, however, add the global explicit tag to the output document:

@Test public void whenDumpACustomType_thenGenerateCorrectYAML() { Customer customer = new Customer(); customer.setAge(45); customer.setFirstName("Greg"); customer.setLastName("McDowell"); Yaml yaml = new Yaml(); StringWriter writer = new StringWriter(); yaml.dump(customer, writer); String expectedYaml = "!!com.baeldung.snakeyaml.Customer {age: 45, contactDetails: null, firstName: Greg,\n homeAddress: null, lastName: McDowell}\n"; assertEquals(expectedYaml, writer.toString()); }

With the above approach, we're still dumping the tag information in YAML document.

This means we have to export our class as a library for any consumer who is deserializing it. In order to avoid the tag name in the output file, we can use the dumpAs() method provided by the library.

So in the above code, we could tweak the following to remove the tag:

yaml.dumpAs(customer, Tag.MAP, null);

6. Conclusion

Artikel ini mengilustrasikan penggunaan pustaka SnakeYAML untuk membuat serial objek Java ke YAML dan sebaliknya.

Semua contoh dapat ditemukan di proyek GitHub - ini adalah proyek berbasis Maven, jadi semestinya mudah untuk mengimpor dan menjalankannya apa adanya.