SnakeYaml快速入門
From: https://www.jianshu.com/p/d8136c913e52
在YAML快速入門[https://www.jianshu.com/p/97222440cd08]中,我們已經簡單介紹了YAML的語法,本節中主要介紹YAML的配置讀取。
目前有很多可以生成和解析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配置片段了。那麼如果再使用上面的程式碼解析,就會報錯:
image.png
可以看到,load方法無法處理---標記。
這種時候只能使用loadAll方法解析:
public Iterable<Object> loadAll(String yaml)
public Iterable<Object> loadAll(InputStream yaml)
public Iterable<Object> 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方法的過載:
image.png
非常明確,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
相關文章
- 快速排序快速入門排序
- 自學前端如何快速入門?怎麼快速入門前端?前端
- SQL快速入門 ( MySQL快速入門, MySQL參考, MySQL快速回顧 )MySql
- MySQL 快速入門MySql
- mysqlsla快速入門MySql
- Pipenv 快速入門
- Envoy 快速入門
- mongodb快速入門MongoDB
- Spark 快速入門Spark
- zookeeper 快速入門
- MQTT 快速入門MQQT
- Lumen快速入門
- Webpack快速入門Web
- RabbitMQ快速入門MQ
- QT快速入門QT
- makefile快速入門
- CSS快速入門CSS
- Markdown快速入門
- Docker快速入門Docker
- mybatis快速入門MyBatis
- Nginx快速入門Nginx
- TypeScript快速入門TypeScript
- TypeScript 快速入門TypeScript
- phpunit 快速入門PHP
- Zookeeper快速入門
- pipenv快速入門
- Thymeleaf【快速入門】
- WebSocket 快速入門Web
- React快速入門React
- vim快速入門
- JavaScript快速入門JavaScript
- 快速入門reactReact
- Express快速入門Express
- GitHub 快速入門Github
- Vue快速入門Vue
- Allure快速入門
- SprinMvc快速入門MVC
- Postman快速入門Postman