1. 概述
本文我们将学习如何使用 SnakeYAML 库 序列化Java对象为YAML文档,以及反向序列化。
2. Maven 依赖
首先添加 SnakeYAML 依赖, 最新版本请查看这里:
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>2.2</version>
</dependency>
3. 初始化
初始化 Yaml 对象:
Yaml yaml = new Yaml();
由于其实现不是线程安全的,因此不同的线程必须有它们自己的 Yaml 实例。
4. 解析 YAML 文档
SnakeYAML 支持从 String 或 InputStream 中加载文档。
假设有下面的 YAML文档,customer.yaml:
firstName: "John"
lastName: "Doe"
age: 20
4.1. 基础用法
下面示例从 ClassPath 目录中加载 yaml 文档:
Yaml yaml = new Yaml();
InputStream inputStream = this.getClass()
.getClassLoader()
.getResourceAsStream("customer.yaml");
Map<String, Object> obj = yaml.load(inputStream);
System.out.println(obj);
输出结果:
{firstName=John, lastName=Doe, age=20}
默认情况下,load() 方法返回的是一个 Map 实例,下节我们学习如何映射为Java Bean。
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.
定义要映射的实体类,Customer class:
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.
Let’s update the document and store it in a new file 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 type*.* The drawback to this approach is that the type has to be exported as a library in order to be used where needed.
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.
Now on loading the customer.yaml, we’ll get the Customer object:
Yaml yaml = new Yaml(new Constructor(Customer.class));
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. 嵌套对象和集合
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.
Now we’ll parse 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
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:
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:
@Test
public void
whenLoadYAMLDocumentWithTopLevelClass_thenLoadCorrectJavaObjectWithNestedObjects() {
Yaml yaml = new Yaml(new Constructor(Customer.class, new LoaderOptions()));
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. 类型安全的集合
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.
Let’s take one Customer having more than one Contact, and try to load it:
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:
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. 加载多个文档
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.
By default, the method returns an instance of Iterable