SnakeYAMLでYAMLを解析する
1. 概要
このチュートリアルでは、SnakeYAMLライブラリをserialize Java objects to YAML documents and vice versaに使用する方法を学習します。
2. プロジェクトのセットアップ
プロジェクトでSnakeYAMLを使用するために、次のMaven依存関係を追加します(最新バージョンはhereにあります)。
org.yaml
snakeyaml
1.21
3. エントリーポイント
Yamlクラスは、APIのエントリポイントです。
Yaml yaml = new Yaml();
実装はスレッドセーフではないため、異なるスレッドには独自のYamlインスタンスが必要です。
4. YAMLドキュメントの読み込み
ライブラリは、StringまたはInputStreamからドキュメントをロードするためのサポートを提供します。 ここでのコードサンプルの大部分は、InputStreamの解析に基づいています。
簡単なYAMLドキュメントを定義し、ファイルにcustomer.yamlという名前を付けることから始めましょう。
firstName: "John"
lastName: "Doe"
age: 20
4.1. 基本的な使い方
次に、上記のYAMLドキュメントをYamlクラスで解析します。
Yaml yaml = new Yaml();
InputStream inputStream = this.getClass()
.getClassLoader()
.getResourceAsStream("customer.yaml");
Map obj = yaml.load(inputStream);
System.out.println(obj);
上記のコードは次の出力を生成します。
{firstName=John, lastName=Doe, age=20}
デフォルトでは、load()メソッドはMapインスタンスを返します。 毎回Mapオブジェクトをクエリするには、プロパティキー名を事前に知っておく必要があります。また、ネストされたプロパティをトラバースするのも簡単ではありません。
4.2. カスタムタイプ
ライブラリもprovides a way to load the document as a custom classです。 このオプションを使用すると、メモリ内のデータを簡単に走査できます。
Customerクラスを定義して、ドキュメントを再度ロードしてみましょう。
public class Customer {
private String firstName;
private String lastName;
private int age;
// getters and setters
}
YAMLドキュメントが既知のタイプとして逆シリアル化されると仮定すると、ドキュメントで明示的なグローバルtagを指定できます。
ドキュメントを更新して、新しいファイルcustomer_with_type.yaml:に保存しましょう
!!com.example.snakeyaml.Customer
firstName: "John"
lastName: "Doe"
age: 20
ドキュメントの最初の行に注意してください。この行には、ロード時に使用されるクラスに関する情報が含まれています。
次に、上記で使用したコードを更新し、新しいファイル名を入力として渡します。
Yaml yaml = new Yaml();
InputStream inputStream = this.getClass()
.getClassLoader()
.getResourceAsStream("yaml/customer_with_type.yaml");
Customer customer = yaml.load(inputStream);
load()メソッドはCustomerタイプ. のインスタンスを返すようになりました*このアプローチの欠点は、必要な場所で使用するためにタイプをライブラリとしてエクスポートする必要があることです*。
ただし、ライブラリをエクスポートする必要のない明示的なローカルタグを使用することもできます。
Another way of loading a custom type is by using the Constructor class。 このようにして、解析するYAMLドキュメントのルートタイプを指定できます。 ルートタイプとしてCustomerタイプを使用してConstructorインスタンスを作成し、それをYamlインスタンスに渡します。
customer.yaml, をロードすると、Customerオブジェクトが取得されます。
Yaml yaml = new Yaml(new Constructor(Customer.class));
4.3. 暗黙のタイプ
In case there’s no type defined for a given property, the library automatically converts the value to an implicit type。
例えば:
1.0 -> Float
42 -> Integer
2009-03-30 -> Date
テストケースを使用して、この暗黙的な型変換をテストしてみましょう。
@Test
public void whenLoadYAML_thenLoadCorrectImplicitTypes() {
Yaml yaml = new Yaml();
Map
4.4. ネストされたオブジェクトとコレクション
Given a top-level type, the library automatically detects the types of nested objectsは、インターフェイスまたは抽象クラスでない限り、ドキュメントを関連するネストされた型に逆シリアル化します。
ContactとAddress detailsをcustomer.yaml,に追加し、新しいファイルをcustomer_with_contact_details_and_address.yaml. として保存しましょう。
次に、新しいYAMLドキュメントを解析します。
firstName: "John"
lastName: "Doe"
age: 31
contactDetails:
- type: "mobile"
number: 123456789
- type: "landline"
number: 456786868
homeAddress:
line: "Xyz, DEF Street"
city: "City Y"
state: "State Y"
zip: 345657
Customerクラスもこれらの変更を反映する必要があります。 更新されたクラスは次のとおりです。
public class Customer {
private String firstName;
private String lastName;
private int age;
private List contactDetails;
private Address homeAddress;
// getters and setters
}
ContactクラスとAddressクラスがどのように見えるか見てみましょう。
public class Contact {
private String type;
private int number;
// getters and setters
}
public class Address {
private String line;
private String city;
private String state;
private Integer zip;
// getters and setters
}
次に、指定されたテストケースでYaml#load()をテストします。
@Test
public void
whenLoadYAMLDocumentWithTopLevelClass_thenLoadCorrectJavaObjectWithNestedObjects() {
Yaml yaml = new Yaml(new Constructor(Customer.class));
InputStream inputStream = this.getClass()
.getClassLoader()
.getResourceAsStream("yaml/customer_with_contact_details_and_address.yaml");
Customer customer = yaml.load(inputStream);
assertNotNull(customer);
assertEquals("John", customer.getFirstName());
assertEquals("Doe", customer.getLastName());
assertEquals(31, customer.getAge());
assertNotNull(customer.getContactDetails());
assertEquals(2, customer.getContactDetails().size());
assertEquals("mobile", customer.getContactDetails()
.get(0)
.getType());
assertEquals(123456789, customer.getContactDetails()
.get(0)
.getNumber());
assertEquals("landline", customer.getContactDetails()
.get(1)
.getType());
assertEquals(456786868, customer.getContactDetails()
.get(1)
.getNumber());
assertNotNull(customer.getHomeAddress());
assertEquals("Xyz, DEF Street", customer.getHomeAddress()
.getLine());
}
4.5. タイプセーフコレクション
特定のJavaクラスの1つ以上のプロパティがタイプセーフ(ジェネリック)コレクションである場合、正しいパラメーター化されたタイプが識別されるように、TypeDescriptionを指定することが重要です。
複数のContactを持つ1つのCustomerを取得して、ロードしてみましょう。
firstName: "John"
lastName: "Doe"
age: 31
contactDetails:
- { type: "mobile", number: 123456789}
- { type: "landline", number: 123456789}
このドキュメントをロードするには、we can specify the TypeDescription for the given property on the top level class:
Constructor constructor = new Constructor(Customer.class);
TypeDescription customTypeDescription = new TypeDescription(Customer.class);
customTypeDescription.addPropertyParameters("contactDetails", Contact.class);
constructor.addTypeDescription(customTypeDescription);
Yaml yaml = new Yaml(constructor);
4.6. 複数のドキュメントをロードする
1つのFileに複数のYAMLドキュメントがあり、それらすべてを解析したい場合があります。 Yamlクラスは、このようなタイプの解析を行うためのloadAll()メソッドを提供します。
デフォルトでは、メソッドは各オブジェクトのタイプがMap<String, Object>. であるIterable<Object>のインスタンスを返します。カスタムタイプが必要な場合は、上記で説明したようにConstructorインスタンスを使用できます。.
単一ファイル内の次のドキュメントを検討してください。
---
firstName: "John"
lastName: "Doe"
age: 20
---
firstName: "Jack"
lastName: "Jones"
age: 25
以下のコードサンプルに示すように、loadAll()メソッドを使用して上記を解析できます。
@Test
public void whenLoadMultipleYAMLDocuments_thenLoadCorrectJavaObjects() {
Yaml yaml = new Yaml(new Constructor(Customer.class));
InputStream inputStream = this.getClass()
.getClassLoader()
.getResourceAsStream("yaml/customers.yaml");
int count = 0;
for (Object object : yaml.loadAll(inputStream)) {
count++;
assertTrue(object instanceof Customer);
}
assertEquals(2,count);
}
5. YAMLドキュメントのダンプ
ライブラリは、dump a given Java object into a YAML documentへのメソッドも提供します。 出力は、Stringまたは指定されたファイル/ストリームである可能性があります。
5.1. 基本的な使い方
Map<String, Object>のインスタンスをYAMLドキュメント(String)にダンプする簡単な例から始めます。
@Test
public void whenDumpMap_thenGenerateCorrectYAML() {
Map data = new LinkedHashMap();
data.put("name", "Silenthand Olleander");
data.put("race", "Human");
data.put("traits", new String[] { "ONE_HAND", "ONE_EYE" });
Yaml yaml = new Yaml();
StringWriter writer = new StringWriter();
yaml.dump(data, writer);
String expectedYaml = "name: Silenthand Olleander\nrace: Human\ntraits: [ONE_HAND, ONE_EYE]\n";
assertEquals(expectedYaml, writer.toString());
}
上記のコードは次の出力を生成します(LinkedHashMapのインスタンスを使用すると、出力データの順序が保持されることに注意してください)。
name: Silenthand Olleander
race: Human
traits: [ONE_HAND, ONE_EYE]
5.2. カスタムJavaオブジェクト
dump custom Java types into an output streamを選択することもできます。 ただし、これにより、グローバルな明示的なtagが出力ドキュメントに追加されます。
@Test
public void whenDumpACustomType_thenGenerateCorrectYAML() {
Customer customer = new Customer();
customer.setAge(45);
customer.setFirstName("Greg");
customer.setLastName("McDowell");
Yaml yaml = new Yaml();
StringWriter writer = new StringWriter();
yaml.dump(customer, writer);
String expectedYaml = "!!com.example.snakeyaml.Customer {age: 45, contactDetails: null, firstName: Greg,\n homeAddress: null, lastName: McDowell}\n";
assertEquals(expectedYaml, writer.toString());
}
上記のアプローチでは、YAMLドキュメントにタグ情報をダンプしています。
つまり、クラスをデシリアライズしているすべてのコンシューマーのライブラリとしてエクスポートする必要があります。 出力ファイルでタグ名を回避するために、ライブラリが提供するdumpAs()メソッドを使用できます。
したがって、上記のコードでは、次を調整してタグを削除できます。
yaml.dumpAs(customer, Tag.MAP, null);
6. 結論
この記事では、SnakeYAMLライブラリーを使用してJavaオブジェクトをYAMLにシリアル化し、その逆を行う方法を説明しました。
すべての例はthe GitHub projectにあります。これはMavenベースのプロジェクトであるため、そのままインポートして実行するのは簡単です。