Analyse de YAML avec SnakeYAML

1. Vue d'ensemble

Dans ce didacticiel, nous allons apprendre à utiliser la bibliothèque SnakeYAML pour sérialiser des objets Java en documents YAML et 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ée ici):

 org.yaml snakeyaml 1.21 

3. Point d'entrée

La classe Yaml 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 instance Yaml .

4. Chargement d'un document YAML

La bibliothèque prend en charge le chargement du document à partir d'une chaîne ou d'un InputStream . La majorité des exemples de code ici serait basée sur l'analyse de InputStream .

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

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

4.1. Utilisation de base

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

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éthode load () renvoie une instance de Map . Interroger l' objet Map à 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 fournit également un moyen de charger le document en tant que classe personnalisée . Cette option permettrait une traversée facile des données en mémoire.

Définissons une classe Customer et essayons de charger à nouveau 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 une balise globale explicite dans le document.

Mettons à jour le document et le stockons dans un nouveau fichier customer_with_type.yaml:

!!com.baeldung.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éthode load () renvoie désormais une instance de type Customer . 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.

Une autre façon de charger un type personnalisé consiste à utiliser la classe Constructor . De cette façon, nous pouvons spécifier le type racine d'un document YAML à analyser. Créons une instance Constructor avec le type Customer comme type racine et passons-la à l' instance Yaml .

Maintenant, lors du chargement du fichier customer.yaml, nous obtenons l' objet Customer :

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

4.3. Types implicites

Dans le cas où aucun type n'est défini pour une propriété donnée, la bibliothèque convertit automatiquement la valeur en un type implicite .

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

Étant donné un type de niveau supérieur, la bibliothèque détecte automatiquement les types d'objets imbriqués , sauf s'il s'agit d'une interface ou d'une classe abstraite, et désérialise le document dans le type imbriqué approprié.

Nous allons ajouter contact et adresse des détails à la customer.yaml, et enregistrer le nouveau fichier customer_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 classe de client 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 classes Contact et 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 }

Nous allons maintenant tester le Yaml # 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

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

Cet article a illustré les utilisations de la bibliothèque SnakeYAML pour sérialiser des objets Java en YAML et vice versa.

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