YAML mit SnakeYAML analysieren

Analysieren von YAML mit SnakeYAML

1. Überblick

In diesem Tutorial erfahren Sie, wie Sie die Bibliothek vonSnakeYAMLfürserialize Java objects to YAML documents and vice versa verwenden.

2. Projektaufbau

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


    org.yaml
    snakeyaml
    1.21

3. Einstiegspunkt

Die KlasseYamlist der Einstiegspunkt für die API:

Yaml yaml = new Yaml();

Da die Implementierung nicht threadsicher ist, müssen verschiedene Threads ihre eigeneYaml-Instanz haben.

4. Laden eines YAML-Dokuments

Die Bibliothek bietet Unterstützung beim Laden des Dokuments vonString oderInputStream. Die Mehrheit der Codebeispiele hier würde auf dem Parsen derInputStream basieren.

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

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

4.1. Grundsätzliche Verwendung

Jetzt analysieren wir das obige YAML-Dokument mit der KlasseYaml:

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 Methodeload()eine Instanz vonMapzurück. Wenn Sie dasMap-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 auchprovides a way to load the document as a custom class. Diese Option würde ein einfaches Durchlaufen von Daten im Speicher ermöglichen.

Definieren wir eineCustomer-Klasse 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 explizite globaletag angeben.

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

!!com.example.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 Methodeload() gibt jetzt eine Instanz vonCustomer Typ 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.

Another way of loading a custom type is by using the Constructor class. Auf diese Weise können wir den Stammtyp für ein zu analysierendes YAML-Dokument angeben. Erstellen wir eineConstructor-Instanz mit demCustomer-Typ als Root-Typ und übergeben Sie sie an dieYaml-Instanz.

Beim Laden voncustomer.yaml, erhalten Sie nun dasCustomer-Objekt:

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

4.3. Implizite Typen

In case there’s no type defined for a given property, the library automatically converts the value to an implicit type.

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

Given a top-level type, the library automatically detects the types of nested objects, es sei denn, es handelt sich um eine Schnittstelle oder eine abstrakte Klasse, und deserialisiert das Dokument in den entsprechenden verschachtelten Typ.

Fügen wirContact undAddress details zucustomer.yaml, hinzu und speichern Sie die neue Datei alscustomer_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 Klasse vonCustomerollte 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 die KlassenContact undAddressaussehen:

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 dieYaml #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

Wenn eine oder mehrere Eigenschaften einer bestimmten Java-Klasse typsichere (generische) Sammlungen sind, ist es wichtig,TypeDescriptionanzugeben, damit der richtige parametrisierte Typ identifiziert wird.

Nehmen wir einCustomer mit mehr als einemContact und versuchen, es zu laden:

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

Um dieses Dokument zu laden, müssenwe 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. Laden mehrerer Dokumente

Es kann Fälle geben, in denen in einem einzigenFile mehrere YAML-Dokumente vorhanden sind und wir alle analysieren möchten. Die KlasseYaml bietet eine MethodeloadAll(), um eine solche Art der Analyse durchzuführen.

Standardmäßig gibt die Methode eine Instanz vonIterable<Object> zurück, wobei jedes Objekt vom TypMap<String, Object>. ist. Wenn ein benutzerdefinierter Typ gewünscht wird, können wir die InstanzConstructorwie oben beschrieben verwenden

Betrachten Sie die folgenden Dokumente in einer einzelnen Datei:

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

Wir können das Obige mit derloadAll()-Methode analysieren, wie im folgenden Codebeispiel gezeigt:

@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-Dokumente sichern

Die Bibliothek bietet auch eine Methode fürdump a given Java object into a YAML document. Die Ausgabe kannString oder eine angegebene Datei / ein bestimmter Stream sein.

5.1. Grundsätzliche Verwendung

Wir beginnen mit einem einfachen Beispiel für das Speichern einer Instanz vonMap<String, Object> in einem YAML-Dokument (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());
}

Der obige Code erzeugt die folgende Ausgabe (beachten Sie, dass bei Verwendung einer Instanz vonLinkedHashMap die Reihenfolge der Ausgabedaten beibehalten wird):

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

5.2. Benutzerdefinierte Java-Objekte

Wir können auchdump custom Java types into an output stream wählen. Dadurch werden jedoch die globalen explizitentag zum Ausgabedokument hinzugefügt:

@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());
}

Mit dem oben beschriebenen Ansatz werden die Tag-Informationen weiterhin im YAML-Dokument gespeichert.

Dies bedeutet, dass wir unsere Klasse als Bibliothek für jeden Verbraucher exportieren müssen, der sie deserialisiert. Um den Tag-Namen in der Ausgabedatei zu vermeiden, können wir die von der Bibliothek bereitgestellte MethodedumpAs()verwenden.

Im obigen Code könnten wir also Folgendes optimieren, um das Tag zu entfernen:

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

6. Fazit

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

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