YAML parseren met SnakeYAML

1. Overzicht

In deze zelfstudie leren we hoe u de SnakeYAML-bibliotheek gebruikt voor serialiseer Java-objecten naar YAML-documenten en vice versa.

2. Projectconfiguratie

Om SnakeYAML in ons project te gebruiken, zullen we de volgende Maven-afhankelijkheid toevoegen (de laatste versie is hier te vinden):

 org.yaml snakeyaml 1.21 

3. Toegangspunt

De Yaml class is het toegangspunt voor de API:

Yaml yaml = nieuwe Yaml ();

Omdat de implementatie niet thread-safe is, moeten verschillende threads hun eigen thread hebben Yaml voorbeeld.

4. Laden van een YAML-document

De bibliotheek biedt ondersteuning voor het laden van het document vanuit een Draad of een InputStream. De meerderheid van de codevoorbeelden hier zou zijn gebaseerd op het parseren van het InputStream.

Laten we beginnen met het definiëren van een eenvoudig YAML-document en het bestand een naam geven als customer.yaml:

firstName: "John" lastName: "Doe" leeftijd: 20

4.1. Basisgebruik

Nu zullen we het bovenstaande YAML-document ontleden met de Yaml klasse:

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

De bovenstaande code genereert de volgende output:

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

Standaard is het laden() methode retourneert een Kaart voorbeeld. Het opvragen van het Kaart object elke keer vereist dat we de sleutelnamen van de eigenschap van tevoren weten, en het is ook niet gemakkelijk om geneste eigenschappen te doorlopen.

4.2. Aangepast type

De bibliotheek ook biedt een manier om het document als een aangepaste klasse te laden. Met deze optie kunnen gegevens in het geheugen gemakkelijk worden doorlopen.

Laten we een Klant class en probeer het document opnieuw te laden:

openbare klasse Klant {private String firstName; private String achternaam; privé int leeftijd; // getters en setters}

Ervan uitgaande dat het YAML-document gedeserialiseerd moet worden als een bekend type, kunnen we een expliciete global specificeren label in het document.

Laten we het document bijwerken en opslaan in een nieuw bestand customer_with_type.yaml:

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

Let op de eerste regel in het document, die de informatie bevat over de klasse die moet worden gebruikt bij het laden ervan.

Nu zullen we de hierboven gebruikte code bijwerken en de nieuwe bestandsnaam als invoer doorgeven:

Yaml yaml = nieuwe Yaml (); InputStream inputStream = this.getClass () .getClassLoader () .getResourceAsStream ("yaml / customer_with_type.yaml"); Klant klant = yaml.load (inputStream); 

De laden() methode retourneert nu een instantie van Klant type. Het nadeel van deze benadering is dat het type als bibliotheek moet worden geëxporteerd om waar nodig te worden gebruikt.

Hoewel we de expliciete lokale tag kunnen gebruiken waarvoor we niet verplicht zijn om bibliotheken te exporteren.

Een andere manier om een ​​aangepast type te laden, is door de Constructor klasse. Op deze manier kunnen we het root-type specificeren voor een YAML-document dat moet worden geparseerd. Laten we een Constructor instantie met de Klant typ als root-type en geef het door aan het Yaml voorbeeld.

Nu bij het laden van het klant.yaml, we krijgen de Klant voorwerp:

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

4.3. Impliciete typen

Als er geen type is gedefinieerd voor een bepaalde eigenschap, converteert de bibliotheek de waarde automatisch naar een impliciet type.

Bijvoorbeeld:

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

Laten we deze impliciete typeconversie testen met behulp van een testcase:

@Test openbare ongeldigheid whenLoadYAML_thenLoadCorrectImplicitTypes () {Yaml yaml = nieuwe Yaml (); Kaartdocument = yaml.load ("3.0: 2018-07-22"); assertNotNull (document); assertEquals (1, document.size ()); assertTrue (document.containsKey (3.0d)); }

4.4. Geneste objecten en verzamelingen

Gegeven een type op het hoogste niveau, detecteert de bibliotheek automatisch de typen geneste objecten, tenzij ze een interface of een abstracte klasse zijn, en het document deserialiseert naar het relevante geneste type.

Laten we toevoegen Contact en Adres details naar de klant.yaml, en sla het nieuwe bestand op als klant_met_contactdetails_en_adres.yaml.

Nu zullen we het nieuwe YAML-document ontleden:

firstName: "John" lastName: "Doe" leeftijd: 31 contactDetails: - type: "mobiel" nummer: 123456789 - type: "vast" nummer: 456786868 homeAddress: line: "Xyz, DEF Street" city: "City Y" state : "State Y" zip: 345657 

Klant class moet ook deze veranderingen weerspiegelen. Hier is de bijgewerkte klas:

openbare klasse Klant {private String firstName; private String achternaam; privé int leeftijd; privé Lijst contactDetails; privé adres homeAddress; // getters en setters} 

Laten we eens kijken hoe Contact en Adres klassen zien eruit als:

openbare klasse Contact {privé String-type; privé int nummer; // getters en setters}
openbare klasse Adres {privé String-regel; particuliere String stad; private String staat; privé Geheel getal zip; // getters en setters}

Nu gaan we de Yaml#laden() met de gegeven testcase:

@Test openbare leegte whenLoadYAMLDocumentWithTopLevelClass_thenLoadCorrectJavaObjectWithNestedObjects () {Yaml yaml = nieuwe Yaml (nieuwe Constructor (Customer.class)); InputStream inputStream = this.getClass () .getClassLoader () .getResourceAsStream ("yaml / klant_met_contact_details_en_adres.yaml"); Klant klant = yaml.load (inputStream); assertNotNull (klant); assertEquals ("John", customer.getFirstName ()); assertEquals ("Doe", customer.getLastName ()); assertEquals (31, customer.getAge ()); assertNotNull (customer.getContactDetails ()); assertEquals (2, customer.getContactDetails (). size ()); assertEquals ("mobiel", customer.getContactDetails () .get (0) .getType ()); assertEquals (123456789, customer.getContactDetails () .get (0) .getNumber ()); assertEquals ("vaste lijn", customer.getContactDetails () .get (1) .getType ()); assertEquals (456786868, klant.getContactDetails () .get (1) .getNumber ()); assertNotNull (customer.getHomeAddress ()); assertEquals ("Xyz, DEF Street", customer.getHomeAddress () .getLine ()); }

4.5. Type-veilige verzamelingen

Als een of meer eigenschappen van een bepaalde Java-klasse type-veilige (generieke) verzamelingen zijn, is het belangrijk om de Type Beschrijving zodat het juiste geparametriseerde type wordt geïdentificeerd.

Laten we er een nemen Klant met meer dan één Contact, en probeer het te laden:

firstName: "John" lastName: "Doe" leeftijd: 31 contactDetails: - {type: "mobile", nummer: 123456789} - {type: "vaste lijn", nummer: 123456789}

Om dit document te laden, we kunnen de Type Beschrijving voor de gegeven woning op het hoogste niveau:

Constructor constructor = nieuwe Constructor (Customer.class); TypeDescription customTypeDescription = nieuwe TypeDescription (Customer.class); customTypeDescription.addPropertyParameters ("contactDetails", Contact.class); constructor.addTypeDescription (customTypeDescription); Yaml yaml = nieuwe Yaml (constructor);

4.6. Meerdere documenten plaatsen

Er kunnen gevallen zijn waarin, in een enkele het dossier er zijn verschillende YAML-documenten, en we willen ze allemaal ontleden. De Yaml klasse biedt een loadAll () methode om een ​​dergelijk type parsing uit te voeren.

Standaard retourneert de methode een exemplaar van Herhaalbaar waarbij elk object van het type is Kaart. Als een aangepast type gewenst is, kunnen we de Constructor instantie zoals hierboven besproken.

Beschouw de volgende documenten in één bestand:

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

We kunnen het bovenstaande ontleden met behulp van de loadAll () methode zoals weergegeven in het onderstaande codevoorbeeld:

@Test openbare leegte whenLoadMultipleYAMLDocuments_thenLoadCorrectJavaObjects () {Yaml yaml = nieuwe Yaml (nieuwe Constructor (Customer.class)); InputStream inputStream = this.getClass () .getClassLoader () .getResourceAsStream ("yaml / customers.yaml"); int count = 0; voor (Object-object: yaml.loadAll (inputStream)) {count ++; assertTrue (objectinstantie van Klant); } assertEquals (2, count); }

5. Het dumpen van YAML-documenten

De bibliotheek biedt ook een methode om dump een bepaald Java-object in een YAML-document. De uitvoer kan een Draad of een gespecificeerd bestand / stream.

5.1. Basisgebruik

We beginnen met een eenvoudig voorbeeld van het dumpen van een exemplaar van Kaart naar een YAML-document (Draad):

@Test openbare leegte whenDumpMap_thenGenerateCorrectYAML () {Kaartgegevens = nieuwe LinkedHashMap (); data.put ("naam", "Silenthand Olleander"); data.put ("race", "Human"); data.put ("traits", new String [] {"ONE_HAND", "ONE_EYE"}); Yaml yaml = nieuwe Yaml (); StringWriter-schrijver = nieuwe StringWriter (); yaml.dump (data, schrijver); String verwachteYaml = "naam: Silenthand Olleander \ nrace: Menselijke \ nkenmerken: [ONE_HAND, ONE_EYE] \ n"; assertEquals (verwachtYaml, writer.toString ()); }

De bovenstaande code produceert de volgende uitvoer (merk op dat het gebruik van een instantie van LinkedHashMap behoudt de volgorde van de uitvoergegevens):

naam: Silenthand Olleander ras: Menselijke eigenschappen: [ONE_HAND, ONE_EYE]

5.2. Aangepaste Java-objecten

We kunnen er ook voor kiezen dump aangepaste Java-typen in een uitvoerstroom. Dit zal echter het globale expliciete label naar het uitvoerdocument:

@Test public void whenDumpACustomType_thenGenerateCorrectYAML () {Klant klant = nieuwe klant (); customer.setAge (45); customer.setFirstName ("Greg"); customer.setLastName ("McDowell"); Yaml yaml = nieuwe Yaml (); StringWriter-schrijver = nieuwe StringWriter (); yaml.dump (klant, schrijver); String verwachteYaml = "!! com.baeldung.snakeyaml.Customer {leeftijd: 45, contactDetails: null, voornaam: Greg, \ n homeAddress: null, achternaam: McDowell} \ n"; assertEquals (verwachtYaml, writer.toString ()); }

Met de bovenstaande aanpak dumpen we nog steeds de tag-informatie in het YAML-document.

Dit betekent dat we onze klasse moeten exporteren als een bibliotheek voor elke consument die deze deserialiseert. Om de tagnaam in het uitvoerbestand te vermijden, kunnen we de dumpAs () methode verstrekt door de bibliotheek.

Dus in de bovenstaande code kunnen we het volgende aanpassen om de tag te verwijderen:

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

6. Conclusie

Dit artikel illustreert het gebruik van de SnakeYAML-bibliotheek om Java-objecten te serialiseren naar YAML en vice versa.

Alle voorbeelden zijn te vinden in het GitHub-project - dit is een op Maven gebaseerd project, dus het moet gemakkelijk te importeren en uit te voeren zijn zoals het is.