LoginSignup
13
13

More than 5 years have passed since last update.

SnakeYAML で YAML を扱う

Posted at

概要

Java で YAML を扱ってみます。YAML 操作用ライブラリには、 @hryshtk さんの「JavaのYAMLライブラリ比較」を拝見したところ、SnakeYAMLがよさそうなので、これを使ってみました。

なお、YAMLがどんなものかについては YAML(Wikipedia) へのリンクを記すにとどめます。

依存の追加

ドキュメントだと oss.sonatype からダウンロードするように書いてありますが、Maven Centralにも置いてあったので、そちらを参照します。

build.gradle
dependencies {
    compile 'org.yaml:snakeyaml:1.17'

準備

dump にする用のオブジェクトを用意します。

Person.java
package jp.toastkid.verification;

public class Person {
    String firstName;
    String familyName;
    String nationality;
    int age;
}

dump

オブジェクトを yaml に変換してみます。

Client
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

先ほどの実行結果の文字列からオブジェクトを復元してみます。

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 を引数に持つメソッドが用意されていました。通常はこちらを使うことになるでしょう。

loadAs
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 する際には空のコンストラクタが必要なため、こんなに長くなってしまいました……

Person.java
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 ファイルを用意し、

people.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

参考

  1. JavaのYAMLライブラリ比較
  2. 公式ドキュメント
13
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
13