YAML mit SnakeYAML analysieren

1. Übersicht

In diesem Tutorial erfahren Sie, wie Sie mit der SnakeYAML-Bibliothek Java-Objekte in YAML-Dokumente serialisieren und umgekehrt .

2. Projekteinrichtung

Um SnakeYAML in unserem Projekt verwenden zu können, fügen wir die folgende Maven-Abhängigkeit hinzu (die neueste Version finden Sie hier):

 org.yaml snakeyaml 1.21 

3. Einstiegspunkt

Die Yaml- Klasse ist der Einstiegspunkt für die API:

Yaml yaml = new Yaml();

Da die Implementierung nicht threadsicher ist, müssen verschiedene Threads eine eigene Yaml- Instanz haben.

4. Laden eines YAML-Dokuments

Die Bibliothek bietet Unterstützung beim Laden des Dokuments aus einem String oder einem InputStream . Die Mehrheit der Codebeispiele hier würde auf dem Parsen des InputStream basieren .

Beginnen wir damit, ein einfaches YAML-Dokument zu definieren und die Datei als customer.yaml zu benennen :

firstName: "John" lastName: "Doe" age: 20

4.1. Grundlegende Verwendung

Jetzt analysieren wir das obige YAML-Dokument mit der Yaml- Klasse:

Yaml yaml = new Yaml(); InputStream inputStream = this.getClass() .getClassLoader() .getResourceAsStream("customer.yaml"); Map obj = yaml.load(inputStream); System.out.println(obj);

Der obige Code generiert die folgende Ausgabe:

{firstName=John, lastName=Doe, age=20}

Standardmäßig gibt die load () -Methode eine Map- Instanz zurück. Wenn Sie das Map- Objekt jedes Mal abfragen, müssen Sie die Eigenschaftsschlüsselnamen im Voraus kennen, und es ist auch nicht einfach, verschachtelte Eigenschaften zu durchlaufen.

4.2. Benutzerdefinierter Typ

Die Bibliothek bietet auch eine Möglichkeit, das Dokument als benutzerdefinierte Klasse zu laden . Diese Option würde ein einfaches Durchlaufen von Daten im Speicher ermöglichen.

Definieren wir eine Kundenklasse und versuchen Sie, das Dokument erneut zu laden:

public class Customer { private String firstName; private String lastName; private int age; // getters and setters }

Unter der Annahme, dass das YAML-Dokument als bekannter Typ deserialisiert wird, können wir im Dokument ein explizites globales Tag angeben .

Aktualisieren wir das Dokument und speichern es in einer neuen Datei customer_with_type.yaml:

!!com.baeldung.snakeyaml.Customer firstName: "John" lastName: "Doe" age: 20

Beachten Sie die erste Zeile im Dokument, die die Informationen zu der Klasse enthält, die beim Laden verwendet werden soll.

Jetzt aktualisieren wir den oben verwendeten Code und übergeben den neuen Dateinamen als Eingabe:

Yaml yaml = new Yaml(); InputStream inputStream = this.getClass() .getClassLoader() .getResourceAsStream("yaml/customer_with_type.yaml"); Customer customer = yaml.load(inputStream); 

Die load () -Methode gibt jetzt eine Instanz vom Typ Customer zurück . Der Nachteil dieses Ansatzes besteht darin, dass der Typ als Bibliothek exportiert werden muss, um bei Bedarf verwendet zu werden .

Wir könnten jedoch das explizite lokale Tag verwenden, für das wir keine Bibliotheken exportieren müssen.

Eine andere Möglichkeit zum Laden eines benutzerdefinierten Typs ist die Verwendung der Constructor- Klasse . Auf diese Weise können wir den Stammtyp für ein zu analysierendes YAML-Dokument angeben. Lassen Sie uns einen erstellen Constructor mit der Instanz Kundentyp als Stammtyp und an die passieren Yaml Instanz.

Beim Laden von customer.yaml erhalten wir nun das Customer- Objekt:

Yaml yaml = new Yaml(new Constructor(Customer.class));

4.3. Implizite Typen

Falls für eine bestimmte Eigenschaft kein Typ definiert ist, konvertiert die Bibliothek den Wert automatisch in einen impliziten Typ .

Zum Beispiel:

1.0 -> Float 42 -> Integer 2009-03-30 -> Date

Testen wir diese implizite Typkonvertierung anhand eines Testfalls:

@Test public void whenLoadYAML_thenLoadCorrectImplicitTypes() { Yaml yaml = new Yaml(); Map document = yaml.load("3.0: 2018-07-22"); assertNotNull(document); assertEquals(1, document.size()); assertTrue(document.containsKey(3.0d)); }

4.4. Verschachtelte Objekte und Sammlungen

Bei einem Typ der obersten Ebene erkennt die Bibliothek automatisch die Typen verschachtelter Objekte , sofern es sich nicht um eine Schnittstelle oder eine abstrakte Klasse handelt, und deserialisiert das Dokument in den entsprechenden verschachtelten Typ.

Lassen Sie uns hinzufügen Kontakt und Adresse Details zum customer.yaml, und speichern Sie die neue Datei als customer_with_contact_details_and_address.yaml.

Jetzt analysieren wir das neue YAML-Dokument:

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 

Die Kundenklasse sollte diese Änderungen ebenfalls widerspiegeln. Hier ist die aktualisierte Klasse:

public class Customer { private String firstName; private String lastName; private int age; private List contactDetails; private Address homeAddress; // getters and setters } 

Mal sehen, wie Kontakt- und Adressklassen aussehen:

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 }

Jetzt testen wir die Yaml # load () mit dem angegebenen Testfall:

@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. Typensichere Sammlungen

When one or more properties of a given Java class are type-safe (generic) collections, then it's important to specify the TypeDescription so that the correct parameterized type is identified.

Let's take one Customer having more than one Contact, and try to load it:

firstName: "John" lastName: "Doe" age: 31 contactDetails: - { type: "mobile", number: 123456789} - { type: "landline", number: 123456789}

In order to load this document, 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. Loading Multiple Documents

There could be cases where, in a single File there are several YAML documents, and we want to parse all of them. The Yaml class provides a loadAll() method to do such type of parsing.

By default, the method returns an instance of Iterable where each object is of type Map. If a custom type is desired then we can use the Constructor instance as discussed above.

Consider the following documents in a single file:

--- firstName: "John" lastName: "Doe" age: 20 --- firstName: "Jack" lastName: "Jones" age: 25

We can parse the above using the loadAll() method as shown in the below code sample:

@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. Dumping YAML Documents

The library also provides a method to dump a given Java object into a YAML document. The output could be a String or a specified file/stream.

5.1. Basic Usage

We'll start with a simple example of dumping an instance of Map to a YAML document (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()); }

The above code produces the following output (note that using an instance of LinkedHashMap preserves the order of the output data):

name: Silenthand Olleander race: Human traits: [ONE_HAND, ONE_EYE]

5.2. Custom Java Objects

We can also choose to dump custom Java types into an output stream. This will, however, add the global explicit tag to the output document:

@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.baeldung.snakeyaml.Customer {age: 45, contactDetails: null, firstName: Greg,\n homeAddress: null, lastName: McDowell}\n"; assertEquals(expectedYaml, writer.toString()); }

With the above approach, we're still dumping the tag information in YAML document.

This means we have to export our class as a library for any consumer who is deserializing it. In order to avoid the tag name in the output file, we can use the dumpAs() method provided by the library.

So in the above code, we could tweak the following to remove the tag:

yaml.dumpAs(customer, Tag.MAP, null);

6. Conclusion

Dieser Artikel illustrierte die Verwendung der SnakeYAML-Bibliothek zum Serialisieren von Java-Objekten in YAML und umgekehrt.

Alle Beispiele finden Sie im GitHub-Projekt - dies ist ein Maven-basiertes Projekt, daher sollte es einfach zu importieren und auszuführen sein, wie es ist.