Parsing and serializing yaml based on snakeyaml in Java

Time:2021-6-10

This article mainly introduces the implementation of parsing and serializing yaml based on snakeyaml in Java. The sample code is introduced in great detail in this article, which has a certain reference learning value for everyone’s study or work. Friends who need it can refer to it

1. Overview

In this article, we will learn how to use the snakeyaml library to

How to convert yaml document to Java object and how to serialize Java object to yaml document.

2. Project setting

To use snakeyaml in a project, you need to add Maven dependencies (the latest version can be found here): for example


<dependency>
  <groupId>org.yaml</groupId>
  <artifactId>snakeyaml</artifactId>
  <version>1.25</version>
</dependency>

3. Entrance point

The yaml class is the entry point of the API:


Yaml yaml = new Yaml()

Since the implementation is not thread safe, different threads must have their own yaml instances.

4. Loading yaml document

Snakeyaml supports loading documents from string or InputStream. We start by defining a simple yaml document and then name the file customer.yaml:


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

4.1 basic usage

Now we will use the yaml class to parse the above yaml document:


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

The above code generates the following output:


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

By default, the load () method returns a map object. When querying the map object, we need to know the name of the attribute key in advance, otherwise it is easy to make mistakes. A better way is to customize the type.

4.2 custom type resolution

Snakeyaml provides a way to parse documents into custom types

Let’s define a customer class and try to load the document again:


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

Now let’s load:


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

Another way is to use the constructor:


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

4.3 implicit types

If no type is defined for a given property, the library automatically converts the value to an implicit type.

For example:


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

Let’s use a testcase to test this implicit type conversion:


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

4.4 nested objects

Snakeyaml supports nested complex types.

Let’s add the contact and address details to “customer. Yaml” and save the new file as customer_ with_ contact_ details_ and_ address.yaml.。

Now we will analyze the new yaml document:


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

Let’s update the Java class:


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

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
}

Now, let’s test yaml load ():


@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 type safe collection

When one or more properties of a given Java class are generic collection classes, the generic type needs to be specified through typedescription so that it can be resolved correctly.

Let’s assume that a customer has multiple contacts:


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

For correct parsing, we can specify typedescription for a given property on the top-level class:


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 loading multiple files

In some cases, there may be multiple yaml documents in a single file, and we want to parse all of them. The yaml class provides a LOADALL () method to complete this type of parsing.

Suppose the following is in a file:


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

We can use the loadall() method to parse the above content, as shown in the following code example:


@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. Generate yaml file

Snakeyaml supports the serialization of Java objects into YML.

5.1 basic usage

We will start with a simple example of dumping an instance of map < string, Object > to a yaml document (string)


@Test
public void whenDumpMap_thenGenerateCorrectYAML() {
  Map<String, Object> data = new LinkedHashMap<String, Object>();
  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 instances using LinkedHashMap will keep 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 the output stream.


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

Generated content will contain!! Com. Baeldung. Snapyaml. Customer. To avoid using tag names in the output file, we can use the dumpas () method provided by the library.

Therefore, in the above code, we can make the following adjustments to remove the tag:


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

Conclusion 6

This article describes how the snake yaml library parses and serializes yaml documents.

All examples can be found in the GitHub project.

Parsing yaml with snake yaml

The above is the whole content of this article, I hope to help you learn, and I hope you can support developer more.