最近はプロパティファイルを書くときに人間が読みやすくて記述量が少ないという理由でYAML
をよく使っています。
インデントが違うとエラーになりますが、その規約のお陰で記述量の少なさと読みやすさにつながっていると思います。
JavaでYAMLを読み書きするためのライブラリはSpringBootでも使われているSnakeYamlが有名だと思います。
SpringBootは特に何もしなくてもプロパティからJavaObjectを作ってくれるのでSnakeYamlを意識することはあんまりないのですが、今回素で触る機会があったので色々触ってみました。
ソースコードはGitHubをご参照ください。
beeete2/snakeyaml-examples
今回は以下のようなファイルで色々試してみたいと思います。
name: server1 hosts: - hostid: '1' name: host1 items: - itemid: '1' key: item1 startDate: 2017-05-01 endDate: 2017-05-31 - itemid: '2' key: item2 startDate: 2017-05-01 endDate: 2017-05-31
yamlファイルをロード
yamlファイルをロードするときは非常に簡単です。
キーと値のペアのMapに変換されます。リスト型はListに変換されます。
また日付はDate
オブジェクトに変換されるようです。
String text = readDefaultText(); Yaml yaml = new Yaml(); Map map = (Map) yaml.load(text); softly.assertThat(getString(map, "name")).isEqualTo("server1"); List hosts = (List) map.get("hosts"); softly.assertThat(hosts).hasSize(1); Map host = (Map) hosts.get(0); softly.assertThat(getString(host, "name")).isEqualTo("host1"); List items = (List) host.get("items"); softly.assertThat(items).hasSize(2); System.out.println(items); Map item = (Map) items.get(0); softly.assertThat(getString(item, "key")).isEqualTo("item1"); softly.assertThat(item.get("startDate")).isInstanceOf(Date.class); // 日付はDateオブジェクトになる
yamlファイルをロードし任意の型にマッピングする
多分、多くの使い方がこの使い方だと思います。
任意の型にマッピングすることができます。
このときに日付をLocalDateTime
型にマッピングしたいと思います。
任意の型をマッピングするときはConstructor
を継承したクラスを作成してYaml
インスタンス生成時のコンストラクタに指定します。
// LocalDateTimePropertyConstructor class LocalDateTimePropertyConstructor extends Constructor { public LocalDateTimePropertyConstructor() { yamlClassConstructors.put(NodeId.scalar, new TimeStampConstruct()); } class TimeStampConstruct extends Constructor.ConstructScalar { @Override public Object construct(Node node) { if (node.getTag().equals(Tag.TIMESTAMP)) { Construct dateConstructor = yamlConstructors.get(Tag.TIMESTAMP); Date date = (Date) dateConstructor.construct(node); LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.of("UTC")); return localDateTime; } else { return super.construct(node); } } } } // Yamlインスタンスの生成 Yaml yaml = new Yaml(new LocalDateTimePropertyConstructor());
以下のようにマッピングできます。
String text = readDefaultText(); Server server = main.loadAs(text, Server.class); softly.assertThat(server.name).isEqualTo("server1"); softly.assertThat(server.hosts).hasSize(1); Host host = server.hosts.get(0); softly.assertThat(host.name).isEqualTo("host1"); softly.assertThat(host.items).hasSize(2); Item item = host.items.get(0); softly.assertThat(dateFormat(item.startDate)).isEqualTo("2017-05-01T00:00:00"); // LocalDateTimeになる softly.assertThat(dateFormat(item.endDate)).isEqualTo("2017-05-31T00:00:00");// LocalDateTimeになる
もちろんオブジェクトからyamlファイルへダンプすることもできます。
ダンプ
Server server = createServer(); Yaml yaml = new Yaml(); String text = yaml.dump(server); softly.assertThat(text).isNotNull();
こうなります。
!!b3.snakeyaml.entities.Server hosts: - hostid: '1' items: - endDate: {} itemid: '1' key: item1 startDate: {} - endDate: {} itemid: '2' key: item2 startDate: {} name: host1 name: server1
LocalDateTimeの値が変換できていないようです。
対象のオブジェクト(クラス)の完全修飾名が表示されています。
あと、これは好みかもしれませんが、プロパティにインデントがないのが少し気になります。
できれば、こうしたい。
hosts: - hostid: '1' items: - endDate: 2016-05-31 itemid: '1' key: item1 startDate: 2016-05-01 - endDate: 2016-05-31 itemid: '2' key: item2 startDate: 2016-05-01 name: host1 name: server1
書式と任意の型を変換する
まず書式(フォーマット)はDumperOptions
のインスタンスを生成し好みに設定します。
色々設定できるみたいなのですが、インデントとスタイルの設定は以下のようにしました。
DumperOptions options = new DumperOptions(); options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); // ブロックスタイルにする options.setIndicatorIndent(2); // プロパティのインデントを2に設定する options.setIndent(4); // インデントを4に設定する
任意の型の変換はRepresenter
を使って設定します。
LocalDateTimeを変換する例は以下のようになります。
class LocalDateTimeRepresenter extends Representer { public LocalDateTimeRepresenter() { multiRepresenters.put(LocalDateTime.class, new RepresentLocalDateTime()); } private class RepresentLocalDateTime extends RepresentDate { public Node representData(Object data) { LocalDateTime localDateTime = (LocalDateTime) data; String date = localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); return representScalar(getTag(data.getClass(), Tag.TIMESTAMP), date, null); } } }
以下のようになります。
// LocalDateTimeの変換設定 Representer representer = new LocalDateTimeRepresenter(); representer.addClassTag(Server.class, Tag.MAP); // 完全修飾名を表示しない // 書式の設定 DumperOptions options = new DumperOptions(); options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); options.setIndicatorIndent(2); options.setIndent(8); Yaml yaml = new Yaml(representer, options); return yaml.dump(object);
実行すると以下のようになります。
hosts: - hostid: '1' items: - endDate: 2016-05-31 itemid: '1' key: item1 startDate: 2016-05-01 - endDate: 2016-05-31 itemid: '2' key: item2 startDate: 2016-05-01 name: host1 name: server1
まとめ
大体望むようなyamlファイルを生成することができました。
あとはフィールドの並び順の調整が出来ればよかったのですが、そこまでは対応しませんでした。
ちなみに並び順はJavaBeansのアルファベット順になっているみたいです。