Parsing YAML with SnakeYAML – 用SnakeYAML解析YAML

最后修改: 2018年 7月 31日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll learn how to use SnakeYAML library to serialize Java objects to YAML documents and vice versa.

在本教程中,我们将学习如何使用SnakeYAML库来将Java对象序列化为YAML文档,反之亦然

2. Project Setup

2.项目设置

In order to use SnakeYAML in our project, we’ll add the following Maven dependency (the latest version can be found here):

为了在我们的项目中使用SnakeYAML,我们将添加以下Maven依赖项(最新版本可以在这里找到)。

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

3. Entry Point

3.入境点

The Yaml class is the entry point for the API:

Yaml类是API的入口。

Yaml yaml = new Yaml();

Since the implementation is not thread safe, different threads must have their own Yaml instance.

由于实现不是线程安全的,不同的线程必须有自己的Yaml实例。

4. Loading a YAML Document

4.加载一个YAML文档

The library provides support for loading the document from a String or an InputStream. Majority of the code samples here would be based on parsing the InputStream.

该库提供了对从StringInputStream加载文档的支持。这里的大部分代码样本都是基于对InputStream的解析。

Let’s start by defining a simple YAML document, and naming the file as customer.yaml:

让我们从定义一个简单的YAML文件开始,并将文件命名为customer.yaml

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

4.1. Basic Usage

4.1.基本用法

Now we’ll parse the above YAML document with the Yaml class:

现在我们将用Yaml类来解析上述YAML文档。

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 instance. Querying the Map object each time would require us to know the property key names in advance, and it’s also not easy to traverse over nested properties.

默认情况下,load()方法返回一个Map实例。每次查询Map对象都需要我们事先知道属性键名,而且在嵌套的属性上进行遍历也不容易。

4.2. Custom Type

4.2.自定义类型

The library also provides a way to load the document as a custom class. This option would allow easy traversal of data in memory.

该库还提供了一种将文件作为自定义类加载的方法。这个选项将使内存中的数据容易被遍历。

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

让我们定义一个Customer类并尝试再次加载文档。

public class Customer {

    private String firstName;
    private String lastName;
    private int age;

    // getters and setters
}

Assuming the YAML document to be deserialized as a known type, we can specify an explicit global tag in the document.

假设要被反序列化的YAML文档是一个已知的类型,我们可以在文档中明确指定一个全局tag

Let’s update the document and store it in a new file customer_with_type.yaml:

让我们更新文件,并将其存储在一个新的文件customer_with_type.yaml:

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

Note the first line in the document, which holds the info about the class to be used when loading it.

注意文件中的第一行,它保存了加载时要使用的类的信息。

Now we’ll update the code used above, and pass the new file name as input:

现在我们将更新上面使用的代码,并将新的文件名作为输入。

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

The load() method now returns an instance of Customer typeThe drawback to this approach is that the type has to be exported as a library in order to be used where needed.

load() 方法现在返回一个Customer type的实例。这种方法的缺点是必须将该类型作为一个库导出,以便在需要时使用

Although, we could use the explicit local tag for which we aren’t required to export libraries.

虽然,我们可以使用明确的本地标签,我们不需要导出库。

Another way of loading a custom type is by using the Constructor class. This way we can specify the root type for a YAML document to be parsed. Let us create a Constructor instance with the Customer type as root type and pass it to the Yaml instance.

加载自定义类型的另一种方式是通过使用Constructor。这样,我们可以为要解析的YAML文档指定根类型。让我们创建一个Constructor实例,将Customer类型作为根类型,并将其传递给Yaml实例。

Now on loading the customer.yaml, we’ll get the Customer object:

现在在加载customer.yaml时,我们将得到Customer对象。

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

4.3. Implicit Types

4.3.隐式类型

In case there’s no type 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 test this implicit type conversion using a test case:

让我们用一个测试案例来测试这个隐式类型转换。

@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 and Collections

4.4.嵌套对象和集合

Given a top-level type, the library automatically detects the types of nested objects, unless they’re an interface or an abstract class, and deserializes the document into the relevant nested type.

给定一个顶层类型,该库自动检测嵌套对象的类型,除非它们是一个接口或一个抽象类,并将文档反序列化为相关的嵌套类型。

Let’s add Contact and Address details to the customer.yaml, and save the new file as customer_with_contact_details_and_address.yaml. 

让我们将ContactAddressdetails添加到customer.yaml,中,并将新文件保存为customer_with_contact_details_and_address.yaml。

Now we’ll parse the new YAML document:

现在我们将解析新的YAML文档。

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

Customer class should also reflect these changes. Here’s the updated class:

客户类也应该反映这些变化。下面是更新后的类。

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

Let’s see how Contact and Address classes look like:

让我们看看ContactAddress类是怎样的。

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 we’ll test the Yaml#load() with the given test case:

现在我们将用给定的测试案例来测试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 Collections

4.5.类型安全的集合

When one or more properties of a given Java class are type-safe (generic) collections, then it’s important to specify the TypeDescription so that the correct parameterized type is identified.

当一个给定的Java类的一个或多个属性是类型安全(通用)集合时,那么指定TypeDescription 是很重要的,这样可以识别正确的参数化类型。

Let’s take one Customer having more than one Contact, and try to load it:

让我们拿一个有多个ContactCustomer,并尝试加载它。

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

In order to load this document, we can specify the TypeDescription for the given property on the top level class:

为了加载这个文档,我们可以为顶层类上的给定属性指定TypeDescription

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 Documents

4.6.加载多个文件

There could be cases where, in a single File there are several YAML documents, and we want to parse all of them. The Yaml class provides a loadAll() method to do such type of parsing.

在某些情况下,一个文件中有几个YAML文档,而我们想解析所有这些文档。Yaml类提供了一个loadAll()方法来进行这种类型的解析。

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

默认情况下,该方法返回一个Iterable<Object>的实例,其中每个对象的类型为Map<String, Object>。如果需要一个自定义的类型,那么我们可以使用Constructor实例,如上所述

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:

我们可以使用loadAll()方法解析上述内容,如下面的代码示例所示。

@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

5.倾倒YAML文件

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.

该库还提供了一个方法来将一个给定的Java对象转储为YAML文档。输出可以是一个字符串或一个指定的文件/流。

5.1. Basic Usage

5.1.基本用法

We’ll start with a simple example of dumping an instance of Map<String, Object> to a YAML document (String):

我们将从一个简单的例子开始,将一个 Map<String, Object>的实例转储到一个YAML文档(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 using an instance of LinkedHashMap preserves the order of the output data):

上述代码产生以下输出(注意,使用LinkedHashMap的实例可以保留输出数据的顺序)。

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

5.2. Custom Java Objects

5.2.自定义Java对象

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:

我们也可以选择将自定义的Java类型转入输出流。然而,这将在输出文档中添加全局明确的tag

@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.

使用上述方法,我们仍然在YAML文档中倾倒标签信息。

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.

这意味着我们必须将我们的类作为一个库导出,供任何对其进行反序列化的消费者使用。为了避免在输出文件中出现标签名,我们可以使用库中提供的dumpAs()方法。

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

因此,在上面的代码中,我们可以调整以下内容,以删除该标签。

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

6. Conclusion

6.结论

This article illustrated usages of SnakeYAML library to serialize Java objects to YAML and vice versa.

本文说明了SnakeYAML库将Java对象序列化为YAML的用途,反之亦然。

All of the examples can be found in the GitHub project – this is a Maven based project, so it should be easy to import and run as it is.

所有的例子都可以在GitHub项目中找到–这是一个基于Maven的项目,所以应该很容易导入并按原样运行。