これは、なにをしたくて書いたもの?
YAMLというファイルフォーマットはよく扱っていますが、そういえばYAMLをプログラムで扱うことをあまりやってこなかったので、
1度ライブラリーを調べる意味も含めて見てみました。
今回はSnakeYAMLを扱ってみようと思います。
JavaでYAMLを扱う場合のライブラリー
YAMLを扱うライブラリーはなにかのフレームワークに含まれていることが多く、だいたいそちらをそのまま使う気がします。
で、Javaではどのようなものがあるかというと次の2つでしょうか。
- GitHub - snakeyaml/snakeyaml: Mirror of https://bitbucket.org/snakeyaml/snakeyaml
- GitHub - FasterXML/jackson-dataformats-text: Uber-project for (some) standard Jackson textual format backends: csv, properties, yaml (xml to be added in future)
またyaml.orgの「YAML Frameworks and Tools」を見ると、各言語向けのライブラリーが載っていたりします。
Jacksonを使っている場合はjackson-dataformats-textでよいのかなと思いますが、そうではない場合はSnakeYAMLでしょうか。
SnakeYAMLのよいところは、依存関係を持たないところですね。
ちなみに、よくよく見るとjackson-dataformats-textは内部的にはSnakeYAML(3系の場合はSnakeYAML Engine)を使っているようです。
SnakeYAML/SnakeYAML Engine
GitHub - snakeyaml/snakeyaml: Mirror of https://bitbucket.org/snakeyaml/snakeyaml
こちらはミラーで、本体はBitBucketにあります。
ドキュメントはWikiになります。
SnakeYAML / Wiki / Documentation
a complete YAML 1.1 processor. (If you need YAML 1.2 support have a look here)
YAML 1.2を使う場合はSnakeYAML Engineを使います。
GitHub - snakeyaml/snakeyaml-engine: Mirror of https://bitbucket.org/snakeyaml/snakeyaml-engine
BitBucketではこちら。
ドキュメントはこちら。
SnakeYAML Engine / Wiki / Documentation
ライブラリーが別々になっているのは、YAML 1.1と1.2で非互換があるなどの理由のようです。
YAML Ain’t Markup Language (YAML™) revision 1.2.2
Important notes for migration to YAML 1.2
またターゲットにしている機能が違うようで、SnakeYAMLはListやMapなどだけではなくJavaBeansとの変換が可能なようですが、
SnakeYAML EngineはJavaの基本的な型への変換にとどまりJavaBeansや他のカスタムインスタンスへの変換はできません。
high-level API for serializing and deserializing native Java objects.
https://bitbucket.org/snakeyaml/snakeyaml/src/snakeyaml-2.5/
The Engine will parse/emit basic Java structures (String, List
, Map<String, Boolean>). JavaBeans or any other custom instances are explicitly out of scope. Low-level API for serializing and deserializing native Java objects.
https://bitbucket.org/snakeyaml/snakeyaml-engine/src/snakeyaml-engine-2.10/
SnakeYAML Engineの方は、より低レベルAPIということですね。
ちなみにSnakeYAMLはJava 7以上、SnakeYAML EngineはJava 8以上を要求します。
それぞれ、簡単に使っていってみましょう。
環境
今回の環境はこちら。
$ java --version openjdk 25 2025-09-16 OpenJDK Runtime Environment (build 25+36-Ubuntu-124.04.2) OpenJDK 64-Bit Server VM (build 25+36-Ubuntu-124.04.2, mixed mode, sharing) $ mvn --version Apache Maven 3.9.11 (3e54c93a704957b63ee3494413a2b544fd3d825b) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 25, vendor: Ubuntu, runtime: /usr/lib/jvm/java-25-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "6.8.0-85-generic", arch: "amd64", family: "unix"
SnakeYAML
まずはこのあたりを見ながらSnakeYAMLを使ってみます。
SnakeYAML / Wiki / Documentation
Maven依存関係など。
<properties> <maven.compiler.release>25</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties> <dependencies> <dependency> <groupId>org.yaml</groupId> <artifactId>snakeyaml</artifactId> <version>2.5</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>6.0.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.27.3</version> <scope>test</scope> </dependency> </dependencies>
確認はテストコードで行っていきます。
またJavaBeansにマッピングする場合はこちらを使います。
src/main/java/org/littlewings/snakeyaml/Person.java
package org.littlewings.snakeyaml; public class Person { private String firstName; private String lastName; private int age; // getter/setterは省略 }
YAMLを読み込む
YAMLの読み込みから行ってみます。
SnakeYAML Documentation / Tutorial / Loading YAML
テストコードの雛形はこちら。
src/test/java/org/littlewings/snakeyaml/SnakeYamlLoadTest.java
package org.littlewings.snakeyaml; import java.io.StringReader; import java.util.List; import java.util.Map; import java.util.stream.StreamSupport; import org.junit.jupiter.api.Test; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.Constructor; import org.yaml.snakeyaml.error.YAMLException; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; class SnakeYamlLoadTest { // ここにテストを書く! }
リストをロードしてみます。
@Test void loadAsList() { Yaml yaml = new Yaml(); List<String> languages = yaml.load(""" - Java - Python - JavaScript - Rust """); assertThat(languages).isEqualTo(List.of("Java", "Python", "JavaScript", "Rust")); }
ドキュメントをMapとしてロード。Readerで読み込む例にもしています。
@Test void loadAsMap() { LoaderOptions options = new LoaderOptions(); Yaml yaml = new Yaml(options); StringReader reader = new StringReader(""" firstName: カツオ lastName: 磯野 age: 11 """); Map<String, Object> katsuo = yaml.load(reader); assertThat(katsuo).isEqualTo(Map.of("firstName", "カツオ", "lastName", "磯野", "age", 11)); }
JavaBeansとしてロード。
@Test void loadAsJavaBeans1() { Yaml yaml = new Yaml(new Constructor(Person.class, new LoaderOptions())); Person katsuo = yaml.load(""" firstName: カツオ lastName: 磯野 age: 11 """); assertThat(katsuo.getFirstName()).isEqualTo("カツオ"); assertThat(katsuo.getLastName()).isEqualTo("磯野"); assertThat(katsuo.getAge()).isEqualTo(11); }
SnakeYAML Documentation / Tutorial / Loading YAML / Providing the top level type
こちらでもOKです。
@Test void loadAsJavaBeans2() { Yaml yaml = new Yaml(); Person katsuo = yaml.loadAs(""" firstName: カツオ lastName: 磯野 age: 11 """, Person.class); assertThat(katsuo.getFirstName()).isEqualTo("カツオ"); assertThat(katsuo.getLastName()).isEqualTo("磯野"); assertThat(katsuo.getAge()).isEqualTo(11); }
SnakeYAML Documentation / Tutorial / JavaBeans
オブジェクトのリストの場合。
@Test void loadAsListWithObject1() { Yaml yaml = new Yaml(); List<Map<String, Object>> people = yaml.load(""" - firstName: カツオ lastName: 磯野 age: 11 - firstName: ワカメ lastName: 磯野 age: 9 """); assertThat(people).isEqualTo(List.of( Map.of("firstName", "カツオ", "lastName", "磯野", "age", 11), Map.of("firstName", "ワカメ", "lastName", "磯野", "age", 9) )); }
トップレベルがリストの場合は、List.classでは型パラメーターを指定できないので配列とします。
@Test void loadAsListWithObject2() { Yaml yaml = new Yaml(); Person[] people = yaml.loadAs(""" - firstName: カツオ lastName: 磯野 age: 11 - firstName: ワカメ lastName: 磯野 age: 9 """, Person[].class); assertThat(people).hasSize(2); assertThat(people[0].getFirstName()).isEqualTo("カツオ"); assertThat(people[1].getFirstName()).isEqualTo("ワカメ"); }
ちなみにRecordsは扱えないようです。
src/main/java/org/littlewings/snakeyaml/PersonRecord.java
package org.littlewings.snakeyaml; public record PersonRecord( String firstName, String lastName, int age ) { }
@Test void loadAsRecord() { Yaml yaml = new Yaml(); assertThatThrownBy(() -> yaml.loadAs(""" firstName: カツオ lastName: 磯野 age: 11 """, PersonRecord.class)) .hasCauseExactlyInstanceOf(YAMLException.class) .hasMessage(""" Can't construct a java object for tag:yaml.org,2002:org.littlewings.snakeyaml.PersonRecord; exception=java.lang.NoSuchMethodException: org.littlewings.snakeyaml.PersonRecord.<init>() in 'string', line 1, column 1: firstName: カツオ ^ """); }
複数のドキュメントをロードする場合。
@Test void loadMultipleDocuments() { Yaml yaml = new Yaml(); Iterable<Object> documents = yaml.loadAll(""" --- firstName: カツオ lastName: 磯野 age: 11 --- isbn13: 978-4621303252 title: Effective Java 第3版 price: 4400 --- - Java - Python - JavaScript - Rust """); List<Object> documentsAsList = StreamSupport.stream(documents.spliterator(), false) .toList(); assertThat(documentsAsList).hasSize(3); assertThat(documentsAsList).isEqualTo( List.of( Map.of("firstName", "カツオ", "lastName", "磯野", "age", 11), Map.of("isbn13", "978-4621303252", "title", "Effective Java 第3版", "price", 4400), List.of("Java", "Python", "JavaScript", "Rust") ) ); }
YAMLを出力する
次はYAMLを出力してみます。
SnakeYAML Documentation / Tutorial / Dumping YAML
テストコードの雛形はこちら。
src/test/java/org/littlewings/snakeyaml/SnakeYamlDumpTest.java
package org.littlewings.snakeyaml; import java.io.StringWriter; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.error.YAMLException; import org.yaml.snakeyaml.nodes.Tag; import org.yaml.snakeyaml.representer.Representer; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; class SnakeYamlDumpTest { // ここにテストを書く! }
Listを文字列に変換。
@Test void dumpList() { List<String> languages = List.of("Java", "Python", "JavaScript", "Rust"); Yaml yaml = new Yaml(); String asString = yaml.dump(languages); assertThat(asString).isEqualTo(""" [Java, Python, JavaScript, Rust] """); }
この形式が嫌な場合は、FlowStyleをBLOCKにすればよさそうです。Writerに書き込む例にもしています。
@Test void dumpList2() { List<String> languages = List.of("Java", "Python", "JavaScript", "Rust"); DumperOptions options = new DumperOptions(); options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); Yaml yaml = new Yaml(options); StringWriter writer = new StringWriter(); yaml.dump(languages, writer); assertThat(writer.toString()).isEqualTo(""" - Java - Python - JavaScript - Rust """); }
Mapを文字列に変換。こちらもFlowStyle.BLOCKにしておいた方が見慣れた形式かなと思います。
@Test void dumpMap() { Map<String, Object> katsuo = new LinkedHashMap<>(); katsuo.put("firstName", "カツオ"); katsuo.put("lastName", "磯野"); katsuo.put("age", 11); DumperOptions options = new DumperOptions(); options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); Yaml yaml = new Yaml(options); String asString = yaml.dump(katsuo); assertThat(asString).isEqualTo(""" firstName: カツオ lastName: 磯野 age: 11 """); }
出力する要素の順番を固定する場合は、LinkedHashMapを使えばOKです。
JavaBeansは、なにも指定しない場合はこういう形式になります。
@Test void dumpJavaBeans1() { Person katsuo = new Person(); katsuo.setFirstName("カツオ"); katsuo.setLastName("磯野"); katsuo.setAge(11); Yaml yaml = new Yaml(); String asString = yaml.dump(katsuo); assertThat(asString).isEqualTo(""" !!org.littlewings.snakeyaml.Person {age: 11, firstName: カツオ, lastName: 磯野} """); }
ここまで調整すると、見慣れた感じになります。さすがに要素の出力順は制御できなさそうですが。
@Test void dumpJavaBeans2() { Person katsuo = new Person(); katsuo.setFirstName("カツオ"); katsuo.setLastName("磯野"); katsuo.setAge(11); DumperOptions options = new DumperOptions(); options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); Representer representer = new Representer(options); representer.addClassTag(Person.class, Tag.MAP); Yaml yaml = new Yaml(representer); String asString = yaml.dump(katsuo); assertThat(asString).isEqualTo(""" age: 11 firstName: カツオ lastName: 磯野 """); }
YAML#dumpAsで単純に指定することもできます。
@Test void dumpJavaBeans3() { Person katsuo = new Person(); katsuo.setFirstName("カツオ"); katsuo.setLastName("磯野"); katsuo.setAge(11); Yaml yaml = new Yaml(); String asString = yaml.dumpAs(katsuo, Tag.MAP, DumperOptions.FlowStyle.BLOCK); assertThat(asString).isEqualTo(""" age: 11 firstName: カツオ lastName: 磯野 """); }
さらにこの短縮形。
@Test void dumpJavaBeans4() { Person katsuo = new Person(); katsuo.setFirstName("カツオ"); katsuo.setLastName("磯野"); katsuo.setAge(11); Yaml yaml = new Yaml(); String asString = yaml.dumpAsMap(katsuo); assertThat(asString).isEqualTo(""" age: 11 firstName: カツオ lastName: 磯野 """); }
Recordsはやはりサポートしていません。
@Test void dumpRecord() { PersonRecord katsuo = new PersonRecord("カツオ", "磯野", 11); Yaml yaml = new Yaml(); assertThatThrownBy(() -> yaml.dumpAsMap(katsuo)) .isExactlyInstanceOf(YAMLException.class) .hasMessage("No JavaBean properties found in org.littlewings.snakeyaml.PersonRecord"); }
複数のオブジェクトをそれぞれ別ドキュメントとして書き出す場合。
@Test void dumpMultipleDocuments() { Person katsuo = new Person(); katsuo.setFirstName("カツオ"); katsuo.setLastName("磯野"); katsuo.setAge(11); Map<String, Object> book = new LinkedHashMap<>(); book.put("isbn13", "978-4621303252"); book.put("title", "Effective Java 第3版"); book.put("price", 4400); List<String> lauguages = List.of("Java", "Python", "JavaScript", "Rust"); DumperOptions options = new DumperOptions(); options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); Representer representer = new Representer(options); representer.addClassTag(Person.class, Tag.MAP); Yaml yaml = new Yaml(representer); String asString = yaml.dumpAll(List.of(katsuo, book, lauguages).iterator()); assertThat(asString).isEqualTo(""" age: 11 firstName: カツオ lastName: 磯野 --- isbn13: 978-4621303252 title: Effective Java 第3版 price: 4400 --- - Java - Python - JavaScript - Rust """); }
こんなところでしょうか。
SnakeYAML Engine
今度はSnakeYAML Engineです。
SnakeYAML Engine / Wiki / Documentation
Maven依存関係など。
<properties> <maven.compiler.release>25</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties> <dependencies> <dependency> <groupId>org.snakeyaml</groupId> <artifactId>snakeyaml-engine</artifactId> <version>2.10</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>6.0.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.27.3</version> <scope>test</scope> </dependency> </dependencies>
こちらも確認はテストコードで行います。
SnakeYAMLの時と基本的には同じことをやるのですが、SnakeYAMLと違ってJavaBeansとの変換はできないので少し単純になりますね。
使用するクラスはSnakeYAMLとは変わりますが、使い方(?)自体は似たような感じです。
YAMLを読み込む
SnakeYAML Engineを使ってYAMLを読み込んでみます。
SnakeYAML Engine Documentation / Tutorial / Loading YAML
テストコードの雛形はこちら。
src/test/java/org/littlewings/snakeyaml/SnakeYamlLoadTest.java
package org.littlewings.snakeyaml; import java.io.StringReader; import java.util.List; import java.util.Map; import java.util.stream.StreamSupport; import org.junit.jupiter.api.Test; import org.snakeyaml.engine.v2.api.Load; import org.snakeyaml.engine.v2.api.LoadSettings; import static org.assertj.core.api.Assertions.assertThat; class SnakeYamlLoadTest { // ここにテストを書く! }
YAMLからListへ。
@Test void loadAsList() { LoadSettings settings = LoadSettings.builder().build(); Load load = new Load(settings); List<String> languages = (List<String>) load.loadFromString(""" - Java - Python - JavaScript - Rust """); assertThat(languages).isEqualTo(List.of("Java", "Python", "JavaScript", "Rust")); }
YAMLからMapへ。Readerを使う例にもしています。
@Test void loadAsMap() { LoadSettings settings = LoadSettings.builder().build(); Load load = new Load(settings); StringReader reader = new StringReader(""" firstName: カツオ lastName: 磯野 age: 11 """); Map<String, Object> katsuo = (Map<String, Object>) load.loadFromReader(reader); assertThat(katsuo).isEqualTo(Map.of("firstName", "カツオ", "lastName", "磯野", "age", 11));
オブジェクトのリストの場合。
@Test void loadAsListWithObject() { LoadSettings settings = LoadSettings.builder().build(); Load load = new Load(settings); List<Map<String, Object>> people = (List<Map<String, Object>>) load.loadFromString(""" - firstName: カツオ lastName: 磯野 age: 11 - firstName: ワカメ lastName: 磯野 age: 9 """); assertThat(people).isEqualTo(List.of( Map.of("firstName", "カツオ", "lastName", "磯野", "age", 11), Map.of("firstName", "ワカメ", "lastName", "磯野", "age", 9) )); }
複数のYAMLドキュメントを読み込む場合。
@Test void loadMultipleDocuments() { LoadSettings settings = LoadSettings.builder().build(); Load load = new Load(settings); Iterable<Object> documents = load.loadAllFromString(""" --- firstName: カツオ lastName: 磯野 age: 11 --- isbn13: 978-4621303252 title: Effective Java 第3版 price: 4400 --- - Java - Python - JavaScript - Rust """); List<Object> documentsAsList = StreamSupport.stream(documents.spliterator(), false) .toList(); assertThat(documentsAsList).hasSize(3); assertThat(documentsAsList).isEqualTo( List.of( Map.of("firstName", "カツオ", "lastName", "磯野", "age", 11), Map.of("isbn13", "978-4621303252", "title", "Effective Java 第3版", "price", 4400), List.of("Java", "Python", "JavaScript", "Rust") ) ); }
YAMLを出力する
最後はYAMLの出力です。
SnakeYAML Engine Documentation / Tutorial / Dumping YAML
テストコードの雛形はこちら。
src/test/java/org/littlewings/snakeyaml/SnakeYamlDumpTest.java
package org.littlewings.snakeyaml; import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; import org.snakeyaml.engine.v2.api.Dump; import org.snakeyaml.engine.v2.api.DumpSettings; import org.snakeyaml.engine.v2.api.StreamDataWriter; import org.snakeyaml.engine.v2.api.YamlOutputStreamWriter; import org.snakeyaml.engine.v2.common.FlowStyle; import static org.assertj.core.api.Assertions.assertThat; class SnakeYamlDumpTest { // ここにテストを書く! }
Listを文字列へ。
@Test void dumpList() { List<String> languages = List.of("Java", "Python", "JavaScript", "Rust"); DumpSettings settings = DumpSettings.builder().build(); Dump dump = new Dump(settings); String asString = dump.dumpToString(languages); assertThat(asString).isEqualTo(""" [Java, Python, JavaScript, Rust] """); }
FlowStyleをBLOCKにしてスタイルを変更する場合。StreamDataWriterを使って書き出す例にもしています。
@Test void dumpList2() { List<String> languages = List.of("Java", "Python", "JavaScript", "Rust"); DumpSettings settings = DumpSettings.builder().setDefaultFlowStyle(FlowStyle.BLOCK).build(); Dump dump = new Dump(settings); ByteArrayOutputStream baos = new ByteArrayOutputStream(); StreamDataWriter writer = new YamlOutputStreamWriter(baos, StandardCharsets.UTF_8); dump.dump(languages, writer); assertThat(baos.toString(StandardCharsets.UTF_8)).isEqualTo(""" - Java - Python - JavaScript - Rust """); }
Mapを文字列にする場合。こちらも、出力する要素の順番を固定する場合はLinkedHashMapを使えばOKです。
@Test void dumpMap() { Map<String, Object> katsuo = new LinkedHashMap<>(); katsuo.put("firstName", "カツオ"); katsuo.put("lastName", "磯野"); katsuo.put("age", 11); DumpSettings settings = DumpSettings.builder().setDefaultFlowStyle(FlowStyle.BLOCK).build(); Dump dump = new Dump(settings); String asString = dump.dumpToString(katsuo); assertThat(asString).isEqualTo(""" firstName: カツオ lastName: 磯野 age: 11 """); }
複数のYAMLドキュメントを書き出す場合。
@Test void dumpMultipleDocuments() { Map<String, Object> book = new LinkedHashMap<>(); book.put("isbn13", "978-4621303252"); book.put("title", "Effective Java 第3版"); book.put("price", 4400); List<String> lauguages = List.of("Java", "Python", "JavaScript", "Rust"); DumpSettings settings = DumpSettings.builder().setDefaultFlowStyle(FlowStyle.BLOCK).build(); Dump dump = new Dump(settings); String asString = dump.dumpAllToString(List.of(book, lauguages).iterator()); assertThat(asString).isEqualTo(""" isbn13: 978-4621303252 title: Effective Java 第3版 price: 4400 --- - Java - Python - JavaScript - Rust """); }
こんなところでしょうか。
おわりに
SnakeYAMLおよびSnakeYAML Engineを使って、JavaでYAMLを扱ってみました。
SnakeYAML自体は名前は知っていたのですが直接は使ったことがなく、実際に使ってみるとSnakeYAMLとSnakeYAML Engineの2つに
なっていてYAMLのバージョン差異などの事情があるなどいろいろ勉強になりました。
実際に使う時はJackson経由になったりするのかなとも思うのですが、背後にあるライブラリーとして押さえておいた方がよさそうだなと
思いました。