Y Services - Do you really know Yaml?

Posted by shahansudu on Fri, 26 Jul 2019 21:12:32 +0200

In the Java world, configurations are left to Properties, a module that dates back to the old JDK1.0.

"My God, it was 20 years ago and I was still using it Properties.."

However, the main character of this article is not Properties, but Yaml.This is a favorite of the microservice architecture in the new era, and Yaml seems a bit wet compared to Proerties.
In most projects in the past, we can find traces of Properties profiles, including those used for business attribute configuration, machine-to-machine x x x x x each other, internationalization, and so on.
In a few cases, there are also "mixed" practices, such as:

  • Use Xml to represent some templates
  • Use a Json formatted string
  • Nude running text format, self-parsing applied
    ...

Mixed configurations tend to appear in projects that are full of "bad smells" because code is obsolete, people are gone, and so on, it is difficult to form a unified approach.
However, apart from the simple configuration of Properties attribute files, other methods are used to meet the requirements of complex and diverse configuration.

That's how Yaml responds to this scenario, and many of the pages in the SpringBoot's official document are formatted with Yaml syntax.
Here's a look at Yaml and how it works.

1. What is Yaml

Definition from encyclopedia:
"Yaml is a highly readable, easy-to-use data serialization format that was first published by Clark Evans in 2001."
So Yaml isn't a new thing, it's just a lot of people you've come into contact with before.In addition, Yaml is also supported by a variety of programming languages and frameworks and is very versatile.
In the Java system, the general micro-service framework supports or even prefers Yaml as the preferred configuration language.

What are the characteristics of Yaml itself?Take a look at the following example:

environments:
    dev:
        url: https://dev.example.com
  name: Developer Setup
    prod:
        url: https://another.example.com
        name: My Cool App

This grammatically equivalent roperties is:

environments.dev.url=https://dev.example.com
environments.dev.name=Developer Setup
environments.prod.url=https://another.example.com
environments.prod.name=My Cool App

It can be seen that yaml is relatively more structured and more suitable for representing an object.
It has the following grammatical features:

  • Case Sensitive
  • Use space indentation to represent hierarchical relationships rather than Tab keys, mainly because alignment is required for text presentation on different platforms
  • The number of indented spaces is not important as long as the elements of the same level are aligned to the left
  • Use #beginning as comment line
  • Use the beginning of the connector (-) to describe array elements

Contrast Properties
Properties are a good way to configure Key-Value, including as part of some internationalized content.
However, it is difficult for Properties to represent multilevel nesting relationships, and Yaml can better compensate for this shortboard.

Contrast Json
Yaml and Json do not have many advantages or disadvantages. Both are structured expression languages, but Json is designed to be simple to use and easy to transfer.
Yaml, on the other hand, focuses on readability (more on appearance), and can almost be seen as a "superset" of Json, a more readable (and beautiful) structured format.
In addition, Json is easier to generate and parse, suitable for transmission and interaction in a variety of cross-language, distributed environments; at the same time, Yaml is generally used only for configuration purposes.

The following addresses are available for the definition of Yaml:
http://www.yaml.org/spec/1.2/spec.html

2. Grammar of Yaml

Yaml is very simple, it defines only three elements:

  • Object: A collection of key-value pairs corresponding to HashMap in Java
  • Array: A set of ordered values corresponding to a List in Java
  • Single value: A single, nondivisible value, such as 3, "Jackson"

How objects are represented
The properties and nesting relationships of an object are represented by space indentation alignment as follows:

article:
    title: A person's confession
    author:
        name: Chen Ling
        gender: female

How arrays are represented
The elements of the array are represented by a connector (-), as follows:

article:
    title: A person's confession
    tags:
        - biography
        - Sociology
        - character

The basic unit that makes up the contents of an object or array is a single value. Yaml supports seven types of single values, as follows:

type Example
Character string Bob
Boolean Value true
integer 199
Floating point number 19.91
Null ~
time 2001-12-14T22:14:09.10+08:00
date 2019-01-09

Among them, date and time use ISO 8601 international standard format, the definition of which can be referred to:
https://www.w3.org/TR/NOTE-datetime

Typically, a single value ends in one line.But if you encounter a multi-line string, you can use some special characters to represent it.
For example:

text: |
  Hello
  World

The corresponding results are:

{ text: 'Hello\nWorld\n' }

You can use + to reserve line breaks at the end of a string and - to delete line breaks at the end of a string:

text1: |+
  Hello

text2: |-
  Hello

The corresponding results are:

{ text1: 'Hello\n\n\n', text2: 'Hello' }

In addition, Yaml can support advanced uses such as references, functions, regular expressions, and so on, but they are rarely used in projects.

3. Operating Yaml

The most common component currently used to operate on Yaml is Snake Yaml, which supports the standard version of Yaml 1.1.

The official SpringBoot documentation also describes how to integrate the framework, referring to the following addresses:
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-external-config-loading-yaml

Below is an example of integrating SnakeYaml into a project.

A. Introducing Framework

Add to Maven's pom.xml file:

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

B. Code snippets

Implement Loading Profiles

Loading yaml configuration content from the class path config.yml file is accomplished with the following code:

InputStream inputStream = YamlUtil.class.getClassLoader()
        .getResourceAsStream("config.yml");

Yaml yaml = new Yaml();
Map<String, Object> objectMap = yaml.load(inputStream);
System.out.println(objectMap.get("path"));

Implement object conversion

Define the following Pojo objects:

public static class A{
    private String name = "hello";
    private List<B> bs = new ArrayList<B>();

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<B> getBs() {
        return bs;
    }

    public void setBs(List<B> bs) {
        this.bs = bs;
    }
}

public static class B{
    private String id = UUID.randomUUID().toString();

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

Code that outputs objects to Yaml format through SnakeYaml:

A a = new A();
a.getBs().add(new B());
a.getBs().add(new B());

Yaml yaml = new Yaml();
String aString = yaml.dumpAsMap(a);
System.out.println(aString);

The output is as follows:

bs:
- id: b3688f05-ea7e-436b-bc9a-9c5df555c7fd
- id: 7906224d-8ecc-43b8-bc3b-07985bc18ebd
name: hello

If you want to convert Yaml text to an A object in turn, you can execute the following code:

A a1 = new Yaml().parseToObject(aString, A.class);
...

C. Complete case

Ultimately, we can encapsulate the operations of Yaml documents as a tool class for easy integration in business code.

YamlUtil.java

public class YamlUtil {

    /**
     * Load content from a resource file and resolve to a Map object
     *
     * @param path
     * @return
     */
    public static Map<String, Object> loadToMap(String path) {
        if (StringUtils.isEmpty(path)) {
            return Collections.emptyMap();
        }

        InputStream inputStream = YamlUtil.class.getClassLoader()
                .getResourceAsStream(path);

        Yaml yaml = new Yaml();
        Map<String, Object> objectMap = yaml.load(inputStream);
        return objectMap;
    }

    /**
     * Parsing strings into Map objects
     *
     * @param content
     * @return
     */
    public static Map<String, Object> parseToMap(String content) {
        if (StringUtils.isEmpty(content)) {
            return Collections.emptyMap();
        }

        Yaml yaml = new Yaml();
        Map<String, Object> objectMap = yaml.load(content);
        return objectMap;
    }

    /**
     * Resolve string to class object
     *
     * @param content
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T parseToObject(String content, Class<T> clazz) {
        if (StringUtils.isEmpty(content) || clazz == null) {
            return null;
        }

        Yaml yaml = new Yaml(new Constructor(clazz));
        T object = yaml.load(content);
        return object;
    }

    /**
     * Format Object
     *
     * @param object
     * @return
     */
    public static String format(Object object) {
        Yaml yaml = new Yaml();
        return yaml.dumpAsMap(object);
    }

}

So far, we have finished reading and writing Yaml.Of course, in addition to the Snake Yaml described above, you can also use the popular Jackson components to parse it. No more details here. Interested friends can try it themselves.

Reference Documents

Ruan Yifeng-YAML Language Tutorial:
http://www.ruanyifeng.com/blog/2016/07/yaml.html

Official SnakeYaml Documentation:
https://bitbucket.org/asomov/snakeyaml/wiki/Documentation

Yaml 1.2 Specification:
http://www.yaml.org/spec/1.2/spec.html

SpringBoot-LoadingYaml
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-external-config-loading-yaml

Click to view the Codemaker's SpringBoot tutorial series

Topics: Java JSON SpringBoot Spring