Russell Bateman
August 2023
An implementation with all the details and just a few comments is worth a thousand words. I observe that many (including myself just a few minutes ago) struggle with tutorials and stackoverflow answers for stupid things like paths to class loader resources and, worse, import paths. (I'm looking at you, Baeldung—your worst tutorial yet.)
org.yaml snakeyaml 1.21
firstName: "John" lastName: "Doe" age: 20
package com.windofkeltia.yaml; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.util.Map; import static java.util.Objects.nonNull; import org.junit.Test; import org.yaml.snakeyaml.Yaml; public class SnakeYamlTest { @Test public void testBasicUsage() { Yaml yaml = new Yaml(); InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream( "customer.yaml" ); Map< String, Object > object = yaml.load( inputStream ); System.out.println( object ); }
Output from testBasicUsage() when run:
{firstName=John, lastName=Doe, age=20}
!!com.windofkeltia.yaml.Customer firstName: "John" lastName: "Doe" age: 20
package com.windofkeltia.yaml; public class Customer // get this right: must be a "legal" bean! { private String firstName; private String lastName; private int age; // without these accessors, SnakeYaml will vomit leaving you scratching your head... public String getFirstName() { return firstName; } public void setFirstName( String firstName ) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName( String lastName ) { this.lastName = lastName; } public int getAge() { return age; } public void setAge( int age ) { this.age = age; } // not really necessary, but responsible for System.out.println( customer )... @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append( " firstName: " ).append( firstName ).append( '\n' ); sb.append( " lastName: " ).append( lastName ) .append( '\n' ); sb.append( " age: " ).append( age ) .append( '\n' ); return sb.toString(); } }
Add this test case to the JUnit test suite:
@Test public void testCustomType() { String pathname = "src/test/resources/customer-with-type.yaml"; String content = getLinesInFile( pathname ); ByteArrayInputStream inputStream = new ByteArrayInputStream( content.getBytes() ); Yaml yaml = new Yaml(); Customer customer = yaml.loadAs( inputStream, Customer.class ); System.out.println( "Customer --------\n" + customer ); } private static String getLinesInFile( String pathname ) throws IOException { StringBuilder sb = new StringBuilder(); try ( BufferedReader br = new BufferedReader( new FileReader( pathname ) ) ) { for( String line = br.readLine(); nonNull( line ); line = br.readLine() ) sb.append( line ).append( '\n' ); } return sb.toString(); } }
Output from testCustomType() when run:
Customer -------- firstName: John lastName: Doe age: 20
firstName: "John" lastName: "Doe" age: 31 contacts: - type: "mobile" number: "123 456-7898" - type: "landline" number: "456 786-8688" home: line: "1320 Bedford Avenue" city: "Mayberry" state: "NC" zip: "27030"
package com.windofkeltia.yaml; import java.util.ArrayList; import java.util.List; public class Client { private String firstName; private String lastName; private int age; private List< Contact > contacts = new ArrayList<>(); private Address home; public String getFirstName() { return firstName; } public void setFirstName( String firstName ) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName( String lastName ) { this.lastName = lastName; } public int getAge() { return age; } public void setAge( int age ) { this.age = age; } public List< Contact > getContacts() { return contacts; } public void setContacts( List< Contact > contacts ) { this.contacts = contacts; } public Address getHome() { return home; } public void setHome( Address home ) { this.home = home; } // this method really isn't necessary, but it's responsible for System.out.println( customer )... @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append( " firstName: " ).append( firstName ).append( '\n' ); sb.append( " lastName: " ).append( lastName ) .append( '\n' ); sb.append( " age: " ).append( age ) .append( '\n' ); if( !contacts.isEmpty() ) { sb.append( " contacts:\n" ); for( Contact contact : contacts ) sb.append( " " ).append( contact ) .append( '\n' ); } sb.append( " home:\n" ).append( home ) .append( '\n' ); return sb.toString(); } }
package com.windofkeltia.yaml; public class Contact { private String type; private String number; public String getType() { return type; } public void setType( String type ) { this.type = type; } public String getNumber() { return number; } public void setNumber( String number ) { this.number = number; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append( " type: " ).append( type ).append( '\n' ); sb.append( " number: " ).append( number ).append( '\n' ); return sb.toString(); } }
package com.windofkeltia.yaml; public class Address { private String line; private String city; private String state; private String zip; public String getLine() { return line; } public void setLine( String line ) { this.line = line; } public String getCity() { return city; } public void setCity( String city ) { this.city = city; } public String getState() { return state; } public void setState( String state ) { this.state = state; } public String getZip() { return zip; } public void setZip( String zip ) { this.zip = zip; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append( " line: " ).append( line ) .append( '\n' ); sb.append( " city: " ).append( city ) .append( '\n' ); sb.append( " state: " ).append( state ).append( '\n' ); sb.append( " zip: " ).append( zip ) .append( '\n' ); return sb.toString(); } }
Add this test case to the JUnit test suite:
@Test public void testNestedTypes() throws IOException { String pathname = "src/test/resources/client-with-nested-types.yaml"; String content = getLinesInFile( pathname ); ByteArrayInputStream inputStream = new ByteArrayInputStream( content.getBytes() ); Yaml yaml = new Yaml(); Client client = yaml.loadAs( inputStream, Client.class ); System.out.println( "Client ---------------------------\n" + client ); }
Output from test() when run:
Client --------------------------- firstName: John lastName: Doe age: 31 contacts: type: mobile number: 123 456-7898 type: landline number: 456 786-8688 home: line: 1320 Bedford Avenue city: Mayberry state: NC zip: 27030