Yaml文件的解析工具----SnakeYaml快速入門

原文鏈接:https://blog.csdn.net/qqqq0199181/article/details/83857400

目前有很多可以生成和解析YAML的第三方工具,常見的,如SnakeYaml,jYaml,Jackson等,但是不同的工具功能還是差距較大,比如jYaml就不支持合併(<<)和(—)操作。我們下面來看看Springboot使用的SnakeYaml的基本使用方式。

簡介

SnakeYaml是一個完整的YAML1.1規範Processor,支持UTF-8/UTF-16,支持Java對象的序列化/反序列化,支持所有YAML定義的類型(map,omap,set,常量,具體參考http://yaml.org/type/index.html)。

快速使用

要使用SnakeYaml,首先引入maven依賴:

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

我們來完成一個最簡單的yaml解析例子:

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

結果輸出:

{key=hello yaml}

簡介解釋:

  1. 使用Yaml類,創建一個Yaml對象,所有的解析操作都是從這個對象開始;
  2. 聲明瞭一個yaml的字符串(當然也可以使用yaml文檔等),定義了一個對象:key: hello yaml;
  3. 使用Yaml對象的load方法 public Object load(String yaml)加載一段yaml字符串,返回解析之後的對象;

我們通過打印ret的類型:

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

可以看到,實際創建的是一個Map:LinkedHashMap。

load/loadAll/loadAs 方法使用

Yaml的load方法可以傳入多種參數類型:
public Object load(String yaml)
public Object load(InputStream io)
public Object load(Reader io)

三個方法都是通過傳入的不同類型的內容,解析得到結果對象。需要注意一點的是,SnakeYaml通過讀入的內容是否包含BOM頭來判斷輸入流的編碼格式。如果不包含BOM頭,默認爲UTF-8編碼。

下面再來看一個解析案例,這次使用yaml文件的方式。首先創建一個yaml文件:

#test.yaml
- value1
- value2
- value3

很明顯結果應該是一個List集合,把該文件放到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);
}

打印結果:

[value1, value2, value3]

如果需要加載的yaml文件中包含多個yaml片段,則可以使用loadAll方法加載所有的yaml內容。比如有如下一個yaml文件:

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

這個yaml文件內容很明顯是一個對象(或者map),對象的每一個屬性對應的又是一個對象。要加載這個yaml文件,代碼應該是:

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

打印結果:

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

如果我們稍微修改一下test2.yaml文件:

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

按照YAML規範,這應該是三個yaml配置片段了。那麼如果再使用上面的代碼解析,就會報錯:

在這裏插入圖片描述

可以看到,load方法無法處理—標記。

這種時候只能使用loadAll方法解析:
public Iterable loadAll(String yaml)
public Iterable loadAll(InputStream yaml)
public Iterable loadAll(Reader yaml)

可以看到,loadAll方法返回的是一個Object的迭代對象,那麼其中的每一個Object就是每一個yaml片段解析出來的對象:

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

打印的結果爲:

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

可以看到,test2.yaml被解析成了三個Map。
這裏需要注意一點的是,SnakeYaml是在每一次遍歷的時候(即調用Iteratable的forEach方法的時候),纔會去解析下一個—分割的yaml片段。

上面所有的實例,都是把yaml配置轉化成Map或者Collection,如果我們想直接把yaml配置轉成指定對象呢?下面我們通過三個示例來簡單看一下:

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

有指定的Address模型,我們想把address.yaml內容直接轉化成Address對象:

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

只需要使用Yaml的loadAs方法即可:

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

loadAs方法的第二個參數類型,即是要創建的用於包裝yaml數據的類型。
這是第一種方式,對於常見的對象包裝其實已經完全足夠,我們來看下第二種方式,第二種方式也比較簡單,即使用YAML的!!類型強轉來完成。這次的類型再複雜一些,我們創建一個Person類型:

@Setter
@Getter
@ToString
public class Person {

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

這個Person類型包含了我們上一個示例中的Address類型,來添加一個yaml文件:

#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

注意第一行,我們使用—代表一個yaml文檔的開始,並且立刻使用!!告訴下面的類型爲cn.wolfcode.yaml.demo.domain.Person。這樣配置之後,我們就可以直接使用load方法來加載對象了:

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

我們直接使用load方法加載對象,並直接轉化成Person對象即可。

第三種方式,其實是第一種loadAs方法的實現原理,即在創建Yaml對象時,配置用於映射文檔的root構造器。首先去掉person.yaml第一行配置:

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

實現代碼:

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

可以看到,我們在創建Yaml對象的時候,傳入了一個new Constructor(Person.class)對象,即指定了,yaml文件的root對象使用Person類型。注意這個Constructor是org.yaml.snakeyaml.constructor.Constructor對象。

SnakeYaml還能正確的識別集合中的類型。我們修改Person類:

@Setter
@Getter
@ToString
public class Person {

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

在這裏,address屬性變成了一個類型安全的List,修改我們的person.yaml文件:

--- !!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

我們的address屬性由兩個address構成,我們來看下這種情況下,是否能正確的識別:

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

我們來看下輸出:
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)])
可以看到,確實正確的識別到了address集合中的Address類型。

如果要明確數據類型,可以使用TypeDescription來描述具體的數據類型:

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

可以看到,首先創建了一個Person類型的構造器用於映射yaml文檔根類型,接着創建了一個TypeDescription,並傳入Person類型,代表這個TypeDescription是用來描述Person類型的結構,然後通過putListPropertyType(propertName,propertyType)來指定Person類型的address屬性集合中的類型爲Address類型,最後將這個類型描述註冊到構造器描述中。
TypeDescription類型最常用的兩個方法分別是:

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

分別用於限制List集合屬性類型和Map集合屬性類型,當然,Map類型需要分別指定key和value的值類型。

dump入門

上面簡單的介紹了snakeYaml用於yaml文件的解析,下面簡單通過幾個例子看看怎麼使用snakeYaml生成yaml文件。當然,對於yaml來說,更多的時候是作爲配置文件存在。

首先我們來看一個簡單的生成yaml格式字符串的例子:

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

結果輸出:

{key1: value1, key2: 123}

代碼非常簡單,直接使用Yaml的dump方法,就可以把一個對象輸出到一個Writer中。我們簡單的看一下dump方法的重載:
在這裏插入圖片描述

非常明確,dump用於輸出一個對象,而dumpAll和loadAll方法對應,可以輸出一組對象。

下面我們來測試一個自定義對象的輸出:

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

輸出結果爲:

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

接下來再來演示一個輸出多個對象的情況:

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

輸出結果爲:

!!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}

符合預期。

當然,關於dump方法的更多使用,比如設置生成樣式的DumperOptions,設置Tag格式的Representer等更高級一些的需求,大家可以查看SnakeYaml的開發文檔:https://bitbucket.org/asomov/snakeyaml/wiki/Documentation

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章