概要
Java で YAML を扱ってみます。YAML 操作用ライブラリには、 @hryshtk さんの「JavaのYAMLライブラリ比較」を拝見したところ、SnakeYAMLがよさそうなので、これを使ってみました。
なお、YAMLがどんなものかについては YAML(Wikipedia) へのリンクを記すにとどめます。
依存の追加
ドキュメントだと oss.sonatype からダウンロードするように書いてありますが、Maven Centralにも置いてあったので、そちらを参照します。
dependencies {
compile 'org.yaml:snakeyaml:1.17'
準備
dump にする用のオブジェクトを用意します。
package jp.toastkid.verification;
public class Person {
String firstName;
String familyName;
String nationality;
int age;
}
dump
オブジェクトを yaml に変換してみます。
import org.yaml.snakeyaml.Yaml;
public class SnakeYamlVerificaation {
public static void main(final String[] args) {
final Person p = new Person();
p.firstName = "John";
p.familyName = "Kay";
p.nationality = "UK";
p.age = 28;
final Yaml y = new Yaml();
System.out.println(y.dump(p));
}
}
実行してみると……
Exception in thread "main" org.yaml.snakeyaml.error.YAMLException: No JavaBean properties found in jp.toastkid.verification.Person
at org.yaml.snakeyaml.introspector.PropertyUtils.getPropertiesMap(PropertyUtils.java:87)
at org.yaml.snakeyaml.introspector.PropertyUtils.createPropertySet(PropertyUtils.java:110)
at org.yaml.snakeyaml.introspector.PropertyUtils.getProperties(PropertyUtils.java:102)
at org.yaml.snakeyaml.introspector.PropertyUtils.getProperties(PropertyUtils.java:94)
at org.yaml.snakeyaml.representer.Representer.getProperties(Representer.java:246)
at org.yaml.snakeyaml.representer.Representer$RepresentJavaBean.representData(Representer.java:50)
at org.yaml.snakeyaml.representer.BaseRepresenter.representData(BaseRepresenter.java:105)
at org.yaml.snakeyaml.representer.BaseRepresenter.represent(BaseRepresenter.java:64)
at org.yaml.snakeyaml.Yaml.dumpAll(Yaml.java:242)
at org.yaml.snakeyaml.Yaml.dumpAll(Yaml.java:206)
at org.yaml.snakeyaml.Yaml.dump(Yaml.java:181)
at jp.toastkid.verification.SnakeYamlVerificaation.main(SnakeYamlVerificaation.java:12)
SnakeYAMLで扱うクラスには Getter & Setter が要るようです。 Person クラスに Getter & Setter を(Eclipse の自動生成機能により)追加して再度実行します。
!!jp.toastkid.verification.Person {age: 28, familyName: Kay, firstName: John, nationality: UK}
load
先ほどの実行結果の文字列からオブジェクトを復元してみます。
final Yaml y = new Yaml();
final Person loaded = (Person) y.load("!!jp.toastkid.verification.Person {age: 28, familyName: Kay, firstName: John, nationality: UK}");
System.out.printf("%s %s(%s), age = %d", loaded.firstName, loaded.familyName, loaded.nationality, loaded.age);
読み込むデータのクラスを指定することができないのはちょっと不便に思ったら、ちゃんと loadAs という class を引数に持つメソッドが用意されていました。通常はこちらを使うことになるでしょう。
final Person p1 = y.loadAs("!!jp.toastkid.verification.Person {age: 28, familyName: Kay, firstName: John, nationality: UK}", Person.class);
John Kay(UK), age = 28
複数オブジェクトを扱う
dump
Person を多少拡張し(後述)、List に詰めて dump してみます。
final List<Person> people = Arrays.asList(
new Person.Builder().setFirstName("John").setFamilyName("Kay")
.setNationality("UK").setAge(28).build(),
new Person.Builder().setFirstName("Don").setFamilyName("Hall")
.setNationality("US").setAge(28).build(),
new Person.Builder().setFirstName("Ken").setFamilyName("Rodriguez")
.setNationality("AR").setAge(28).build());
final Yaml y = new Yaml();
System.out.println(y.dump(people));
- !!jp.toastkid.verification.Person {age: 28, familyName: Kay, firstName: John, nationality: UK}
- !!jp.toastkid.verification.Person {age: 28, familyName: Hall, firstName: Don, nationality: US}
- !!jp.toastkid.verification.Person {age: 28, familyName: Rodriguez, firstName: Ken,
nationality: AR}
複数のオブジェクトを dump した yaml を load
こういう時にヒアドキュメントが使えない Java は地味につらいと思います。
final List<Person> loaded = y.loadAs(
"- !!jp.toastkid.verification.Person {age: 28, familyName: Kay, firstName: John, nationality: UK}\n"
+ "- !!jp.toastkid.verification.Person {age: 28, familyName: Hall, firstName: Don, nationality: US}\n"
+ "- !!jp.toastkid.verification.Person {age: 28, familyName: Rodriguez, firstName: Ken,"
+ " nationality: AR}", List.class);
loaded.forEach(p -> System.out.printf("%s %s(%s), age = %d\n",
p.getFirstName(), p.getFamilyName(), p.getNationality(), p.getAge())
John Kay(UK), age = 28
Don Hall(US), age = 28
Ken Rodriguez(AR), age = 28
Person クラスの修正
オブジェクト生成をやりやすくするために Builder Pattern を実装してみたところ、どうも Setter と Getter は両方持たせないとダメなようで、おまけに load する際には空のコンストラクタが必要なため、こんなに長くなってしまいました……
public class Person {
private String firstName;
private String familyName;
private String nationality;
private int age;
public static class Builder {
private String firstName;
private String familyName;
private String nationality;
private int age;
public Builder setFirstName(final String firstName) {
this.firstName = firstName;
return this;
}
public Builder setFamilyName(final String familyName) {
this.familyName = familyName;
return this;
}
public Builder setNationality(final String nationality) {
this.nationality = nationality;
return this;
}
public Builder setAge(final int age) {
this.age = age;
return this;
}
public Person build() {
return new Person(this);
}
}
public Person() {
/* NOP. */
}
private Person(final Builder b) {
this.firstName = b.firstName;
this.familyName = b.familyName;
this.nationality = b.nationality;
this.age = b.age;
}
public String getFirstName() {
return firstName;
}
public String getFamilyName() {
return familyName;
}
public String getNationality() {
return nationality;
}
public int getAge() {
return age;
}
public void setFirstName(final String firstName) {
this.firstName = firstName;
}
public void setFamilyName(final String familyName) {
this.familyName = familyName;
}
public void setNationality(final String nationality) {
this.nationality = nationality;
}
public void setAge(final int age) {
this.age = age;
}
}
YAMLファイルからの読み込み
下記のような yaml ファイルを用意し、
- !!jp.toastkid.verification.Person {age: 28, familyName: Kay, firstName: John, nationality: UK}
- !!jp.toastkid.verification.Person {age: 28, familyName: Hall, firstName: Don, nationality: US}
- !!jp.toastkid.verification.Person {age: 28, familyName: Rodriguez, firstName: Ken,
nationality: AR}
loadAs の引数に FileInputStream を指定します。
public static void main(final String[] args) throws IOException {
final Yaml y = new Yaml();
try (final InputStream in = Files.newInputStream(Paths.get("people.yaml"))) {
final List<Person> loaded = y.loadAs(in, List.class);
loaded.forEach(p -> System.out.printf("%s %s(%s), age = %d\n",
p.getFirstName(), p.getFamilyName(), p.getNationality(), p.getAge())
);
}
}
John Kay(UK), age = 28
Don Hall(US), age = 28
Ken Rodriguez(AR), age = 28