Analyser YAML avec SnakeYAML

Analyser YAML avec SnakeYAML

1. Vue d'ensemble

Dans ce didacticiel, nous allons apprendre à utiliser la bibliothèqueSnakeYAML avecserialize Java objects to YAML documents and vice versa.

2. Configuration du projet

Afin d'utiliser SnakeYAML dans notre projet, nous allons ajouter la dépendance Maven suivante (la dernière version peut être trouvéehere):


    org.yaml
    snakeyaml
    1.21

3. Point d'accès

La classeYaml est le point d'entrée de l'API:

Yaml yaml = new Yaml();

Étant donné que l'implémentation n'est pas thread-safe, différents threads doivent avoir leur propre instanceYaml.

4. Chargement d'un document YAML

La bibliothèque prend en charge le chargement du document à partir d'unString ou d'unInputStream. La majorité des exemples de code ici serait basée sur l'analyse desInputStream.

Commençons par définir un simple document YAML et nommer le fichier commecustomer.yaml:

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

4.1. Utilisation de base

Nous allons maintenant analyser le document YAML ci-dessus avec la classeYaml:

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

Le code ci-dessus génère la sortie suivante:

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

Par défaut, la méthodeload() renvoie une instanceMap. Interroger l’objetMap à chaque fois nous obligerait à connaître à l’avance les noms des clés de propriété et il n’est pas non plus facile de parcourir les propriétés imbriquées.

4.2. Type personnalisé

La bibliothèque aussiprovides a way to load the document as a custom class. Cette option permettrait une traversée facile des données en mémoire.

Définissons une classeCustomer et réessayons de charger le document:

public class Customer {

    private String firstName;
    private String lastName;
    private int age;

    // getters and setters
}

En supposant que le document YAML soit désérialisé en tant que type connu, nous pouvons spécifier untag global explicite dans le document.

Mettons à jour le document et stockons-le dans un nouveau fichiercustomer_with_type.yaml:

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

Notez la première ligne du document, qui contient les informations sur la classe à utiliser lors du chargement.

Nous allons maintenant mettre à jour le code utilisé ci-dessus et transmettre le nouveau nom de fichier en entrée:

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

La méthodeload() retourne maintenant une instance du typeCustomer * L'inconvénient de cette approche est que le type doit être exporté en tant que bibliothèque afin d'être utilisé là où c'est nécessaire *.

Cependant, nous pourrions utiliser la balise locale explicite pour laquelle nous ne sommes pas obligés d'exporter des bibliothèques.

Another way of loading a custom type is by using the Constructor class. De cette façon, nous pouvons spécifier le type de racine pour un document YAML à analyser. Créons une instanceConstructor avec le typeCustomer comme type racine et passons-la à l'instanceYaml.

Maintenant, lors du chargement decustomer.yaml, we’obtiendra l’objetCustomer:

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

4.3. Types implicites

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

Par exemple:

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

Testons cette conversion de type implicite à l'aide d'un cas de test:

@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. Objets imbriqués et collections

Given a top-level type, the library automatically detects the types of nested objects, sauf s'il s'agit d'une interface ou d'une classe abstraite, et désérialise le document dans le type imbriqué approprié.

Ajoutons les détails deContact etAddress  auxcustomer.yaml, et enregistrons le nouveau fichier souscustomer_with_contact_details_and_address.yaml. 

Nous allons maintenant analyser le nouveau document 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

La classeCustomer doit également refléter ces changements. Voici la classe mise à jour:

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

Voyons à quoi ressemblent les classesContact etAddress:

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
}

Nous allons maintenant tester lesYaml #load() avec le cas de test donné:

@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. Collections sécurisées

Lorsqu'une ou plusieurs propriétés d'une classe Java donnée sont des collections de type sécurisé (génériques), il est alors important de spécifier lesTypeDescription afin que le type paramétré correct soit identifié.

Prenons unCustomer ayant plus d'unContact et essayons de le charger:

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

Afin de charger ce 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. Chargement de plusieurs documents

Il peut y avoir des cas où, dans un seulFile, il y a plusieurs documents YAML, et nous voulons tous les analyser. La classeYaml fournit une méthodeloadAll() pour effectuer ce type d'analyse.

Par défaut, la méthode renvoie une instance deIterable<Object> où chaque objet est de typeMap<String, Object>. Si un type personnalisé est souhaité, nous pouvons utiliser l'instanceConstructor comme indiqué ci-dessus

Considérez les documents suivants dans un seul fichier:

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

Nous pouvons analyser ce qui précède en utilisant la méthodeloadAll() comme indiqué dans l'exemple de code ci-dessous:

@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. Décharger des documents YAML

La bibliothèque fournit également une méthode àdump a given Java object into a YAML document. La sortie peut être unString ou un fichier / flux spécifié.

5.1. Utilisation de base

Nous allons commencer par un exemple simple de vidage d'une instance deMap<String, Object> dans un document 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());
}

Le code ci-dessus produit la sortie suivante (notez que l'utilisation d'une instance deLinkedHashMap préserve l'ordre des données de sortie):

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

5.2. Objets Java personnalisés

Nous pouvons également choisir dedump custom Java types into an output stream. Cela ajoutera cependant lestag explicites globaux au document de sortie:

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

Avec l'approche ci-dessus, nous vidons toujours les informations de balise dans le document YAML.

Cela signifie que nous devons exporter notre classe en tant que bibliothèque pour tout consommateur qui la désérialise. Afin d'éviter le nom de la balise dans le fichier de sortie, nous pouvons utiliser la méthodedumpAs() fournie par la bibliothèque.

Donc, dans le code ci-dessus, nous pourrions modifier les éléments suivants pour supprimer la balise:

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

6. Conclusion

Cet article illustre les utilisations de la bibliothèque SnakeYAML pour sérialiser des objets Java en YAML et inversement.

Tous les exemples peuvent être trouvés dansthe GitHub project - il s'agit d'un projet basé sur Maven, il devrait donc être facile à importer et à exécuter tel quel.