Yaml file parsing tool - Snake Yaml Quick Start

Links to the original text: https://blog.csdn.net/qqqq0199181/article/details/83857400

At present, there are many third-party tools that can generate and parse YAML, such as Snake Yaml, jYaml, Jackson and so on. However, different tools still have different functions. For example, jYaml does not support merging (<) and (-) operations. Let's take a look at the basic usage of Snake Yaml for Springboot.

brief introduction

Snake Yaml is a complete YAML 1.1 specification Processor that supports UTF-8/UTF-16, serialization/deserialization of Java objects, and all YAML-defined types (map,omap,set, constant, refer specifically to http://yaml.org/type/index.html).

Quick use

To use Snake Yaml, first introduce maven dependencies:

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

Let's complete a simplest example of yaml parsing:

@Test
public void testLoad() {
    String yamlStr = "key: hello yaml";
    Yaml yaml = new Yaml();
    Object ret = yaml.load(yamlStr);
    System.out.println(ret);
}

Results Output:

{key=hello yaml}

Brief explanation:

  1. Using the Yaml class, create a Yaml object from which all parsing operations begin.
  2. Declare a string of yaml (of course, you can use yaml documents, etc.) and define an object: key: hello yaml;
  3. The load method of Yaml object, public Object load(String yaml), is used to load a yaml string and return the object after parsing.

We print ret types:

System.out.println(ret.getClass().getSimpleName());

As you can see, what is actually created is a Map: LinkedHashMap.

The load/loadAll/loadAs method uses

Yaml's load method can pass in a variety of parameter types:
public Object load(String yaml)
public Object load(InputStream io)
public Object load(Reader io)

The three methods are to parse the result objects through different types of content. One thing to note is that Snake Yaml determines the encoding format of the input stream by whether the content read in contains a BOM header. If BOM headers are not included, UTF-8 encoding is defaulted.

Next, let's look at an example of parsing, this time using yaml files. First create a yaml file:

#test.yaml
- value1
- value2
- value3

Obviously, the result should be a List collection. Put the file under resources:

@Test
public void testType() throws Exception {
    Yaml yaml = new Yaml();
    List<String> ret = (List<String>)yaml.load(this.getClass().getClassLoader()
            .getResourceAsStream("test.yaml"));
    System.out.println(ret);
}

Print results:

[value1, value2, value3]

If the yaml file that needs to be loaded contains multiple yaml fragments, you can use the loadAll method to load all yaml content. For example, there is a yaml file as follows:

#test2.yaml
sample1: 
    r: 10
sample2:
    other: haha
sample3:
    x: 100
    y: 100

The content of the yaml file is obviously an object (or map), and each attribute of the object corresponds to another object. To load the yaml file, the code should be:

@Test
public void test2() throws Exception {
    Yaml yaml = new Yaml();
    Map<String, Object> ret = (Map<String, Object>) yaml.load(this
            .getClass().getClassLoader().getResourceAsStream("test2.yaml"));
    System.out.println(ret);
}

Print results:

{sample1={r=10}, sample2={other=haha}, sample3={x=100, y=100}}

If we modify the test2.yaml file slightly:

---
sample1: 
    r: 10
---
sample2:
    other: haha
--- 
sample3:
    x: 100
    y: 100

According to the YAML specification, this should be three yaml configuration fragments. If you use the above code parsing again, you will report an error:

As you can see, the load method cannot handle the tag.

At this time, only the loadAll method can be used to parse:
public Iterable loadAll(String yaml)
public Iterable loadAll(InputStream yaml)
public Iterable loadAll(Reader yaml)

As you can see, the loadAll method returns an iterative object of Object, and each of these objects is the object parsed by each yaml fragment:

@Test
public void test3() throws Exception {
    Yaml yaml = new Yaml();
    Iterable<Object> ret = yaml.loadAll(this.getClass().getClassLoader()
            .getResourceAsStream("test2.yaml"));
    for (Object o : ret) {
        System.out.println(o);
    }
}

The results of printing are as follows:

{sample1={r=10}}
{sample2={other=haha}}
{sample3={x=100, y=100}}

As you can see, test2.yaml is parsed into three Map s.
One thing to note here is that Snake Yaml parses the next partitioned yaml fragment every time it traverses (that is, when the forEast method of Iteratable is called).

All of the examples above convert the yaml configuration to a Map or Collection. What if we want to convert the yaml configuration directly to a specified object? Let's take a brief look at three examples:

#address.yaml
lines: |
  458 Walkman Dr.
  Suite #292
city: Royal Oak
state: MI
postal: 48046

With the specified Address model, we want to convert address.yaml content directly into Address objects:

@Setter
@Getter
@ToString
public class Address {
    private String lines;
    private String city;
    private String state;
    private Integer postal;
}

Just use Yaml's loadAs method:

@Test
public void testAddress() throws Exception {
    Yaml yaml = new Yaml();
    Address ret = yaml.loadAs(this.getClass().getClassLoader()
            .getResourceAsStream("address.yaml"), Address.class);
    Assert.assertNotNull(ret);
    Assert.assertEquals("MI", ret.getState());
}

The second parameter type of the loadAs method is the type to create for wrapping yaml data.
This is the first way, for the common object packaging is actually enough, let's look at the second way, the second way is relatively simple, even with YAML!! Type strong turn to complete. This time the type is more complex. Let's create a Person type:

@Setter
@Getter
@ToString
public class Person {

    private String given;
    private String family;
    private Address address;
}

This Person type contains the Address type in our previous example to add a yaml file:

#person.yaml
--- !!cn.wolfcode.yaml.demo.domain.Person
given  : Chris
family : Dumars
address:
    lines: |
        458 Walkman Dr.
        Suite #292
    city    : Royal Oak
    state   : MI
    postal  : 48046

Notice the first line, we use - to represent the beginning of a yaml document, and use it immediately!! Tell the following type cn.wolfcode.yaml.demo.domain.Person. With this configuration, we can load the object directly using the load method:

@Test
public void testPerson() throws Exception {
    Yaml yaml = new Yaml();
    Person ret = (Person) yaml.load(this.getClass().getClassLoader()
            .getResourceAsStream("person.yaml"));
    Assert.assertNotNull(ret);
    Assert.assertEquals("MI", ret.getAddress().getState());
}

We use load method to load the object directly and convert it into Person object directly.

The third way is actually the implementation principle of the first loadAs method, which configures the root constructor for mapping documents when creating Yaml objects. First remove the first line configuration of person.yaml:

#person.yaml
given  : Chris
family : Dumars
address:
    lines: |
        458 Walkman Dr.
        Suite #292
    city    : Royal Oak
    state   : MI
    postal  : 48046

Implementation code:

@Test
public void testPerson2() {
    Yaml yaml = new Yaml(new Constructor(Person.class));
    Person ret = (Person) yaml.load(this.getClass().getClassLoader()
            .getResourceAsStream("person.yaml"));
    Assert.assertNotNull(ret);
    Assert.assertEquals("MI", ret.getAddress().getState());
}

As you can see, when we create the Yaml object, we pass in a new Constructor(Person.class) object, which specifies that the root object of the yaml file uses the Person type. Note that this Constructor is an object of org.yaml.snakeyaml.constructor.Constructor.

Snake Yaml also correctly identifies the types in the collection. We modify the Person class:

@Setter
@Getter
@ToString
public class Person {

    private String given;
    private String family;
    private List<Address> address;
}

Here, the address attribute becomes a type-safe List, modifying our person.yaml file:

--- !!cn.wolfcode.yaml.demo.domain.Person
given  : Chris
family : Dumars
address:
    - 
      lines: 458 Walkman
      city    : Royal Oak
      state   : MI
      postal  : 48046
    - 
      lines: 459 Walkman
      city    : Royal Oak
      state   : MI
      postal  : 48046

Our address attribute consists of two address. Let's see if we can correctly identify them in this case:

@Test
public void testTypeDesc(){
    Yaml yaml = new Yaml(new Constructor(Person.class));
    Person ret = (Person) yaml.load(this.getClass().getClassLoader()
            .getResourceAsStream("person.yaml"));
    System.out.println(ret);
}

Let's look at the output:
Person(given=Chris, family=Dumars, address=[Address(lines=458 Walkman, city=Royal Oak, state=MI, postal=48046), Address(lines=459 Walkman, city=Royal Oak, state=MI, postal=48046)])
As you can see, the Address type in the address collection is indeed correctly identified.

If you want to specify the data type, you can use TypeDescription to describe the specific data type:

@Test
public void testTypeDesc() {
    Constructor cons = new Constructor(Person.class);
    TypeDescription td = new TypeDescription(Person.class);
    td.putListPropertyType("address", Address.class);
    cons.addTypeDescription(td);
    
    Yaml yaml = new Yaml();
    Person ret = (Person) yaml.load(this.getClass().getClassLoader()
            .getResourceAsStream("person.yaml"));
    System.out.println(ret);
}

As you can see, first a constructor of Person type is created to map the root type of yaml document, then a TypeDescription is created and passed in to Person type, representing the structure of Person type, and then a Person class is specified by putListProperty Type (propertName, propertyType). The type in the set of address attributes of type 1 is Address type. Finally, the type description is registered with the constructor description.
The two most commonly used methods for TypeDescription types are:

public void putListPropertyType(String property, Class<? extends Object> type)
public void putMapPropertyType(String property, Class<? extends Object> key,
        Class<? extends Object> value)

They are used to restrict the attribute types of List collection and Map collection respectively. Of course, Map type needs to specify the value types of key and value respectively.

Introduction to dump

This article briefly introduces how snake Yaml can be used to parse yaml files. Here are a few examples to show how to use snake Yaml to generate yaml files. Of course, for yaml, more often than not, it exists as a configuration file.

Let's start with a simple example of generating yaml format strings:

@Test
public void testDump1() {
    Map<String, Object> obj = new HashMap<String, Object>();
    obj.put("key1", "value1");
    obj.put("key2", 123);

    Yaml yaml = new Yaml();
    StringWriter sw = new StringWriter();
    yaml.dump(obj, sw);
    System.out.println(sw.toString());
}

Results Output:

{key1: value1, key2: 123}

The code is very simple, using Yaml's dump method directly, you can output an object to a Writer. Let's take a brief look at the overload of the dump method:

Clearly, dump is used to output an object, and dumpAll and loadAll methods correspond to each other and can output a set of objects.

Let's test the output of a custom object:

@Test
public void testDump2() {
    Address adr = new Address();
    adr.setCity("Royal Oak");
    adr.setLines("458 Walkman");
    adr.setPostal(48046);
    adr.setState("MI");

    Yaml yaml = new Yaml();
    StringWriter sw = new StringWriter();
    yaml.dump(adr, sw);
    System.out.println(sw.toString());
}

The output results are as follows:

!!cn.wolfcode.yaml.demo.domain.Address {city: Royal Oak, lines: 458 Walkman, postal: 48046,
state: MI}

Next, I'll show you how to output multiple objects:

@Test
public void testDump3() {
    Address adr = new Address();
    adr.setCity("Royal Oak");
    adr.setLines("458 Walkman");
    adr.setPostal(48046);
    adr.setState("MI");
    
    Address adr2 = new Address();
    adr2.setCity("Royal Oak");
    adr2.setLines("459 Walkman");
    adr2.setPostal(48046);
    adr2.setState("MI");
    
    List<Address> target=new ArrayList<>();
    target.add(adr);
    target.add(adr2);

    Yaml yaml = new Yaml();
    StringWriter sw = new StringWriter();
    yaml.dumpAll(target.iterator(), sw);
    System.out.println(sw.toString());
}

The output results are as follows:

!!cn.wolfcode.yaml.demo.domain.Address {city: Royal Oak, lines: 458 Walkman, postal: 48046,
state: MI}
--- !!cn.wolfcode.yaml.demo.domain.Address {city: Royal Oak, lines: 459 Walkman, postal: 48046,
state: MI}

It meets expectations.

Of course, for more advanced requirements such as setting up dump Options for generating styles and Representer for Tag formats, you can see the development documentation of Snake Yaml: https://bitbucket.org/asomov/snakeyaml/wiki/Documentation

Tags: Attribute encoding Fragment SpringBoot

Posted on Wed, 28 Aug 2019 01:29:26 -0700 by hyzdufan