Snake Yaml (in Java)

Russell Bateman
August 2023

Snake Yaml rudiments

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.)

pom.xml:

  org.yaml
  snakeyaml
  1.21


Basic usage

src/test/java/resources/ customer.yaml:
firstName: "John"
lastName:  "Doe"
age:       20
src/test/java/com/windofkeltia/yaml/ SnakeYamlTest.java:
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}

With a custom type (bean)...

src/test/java/resources/ customer-with-type.yaml:
!!com.windofkeltia.yaml.Customer
firstName: "John"
lastName:  "Doe"
age:       20
src/test/java/com/windofkeltia/yaml/ Customer.java:
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();
  }
}
src/test/java/com/windofkeltia/yaml/ SnakeYamlTest.java:

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

Extended to handle nested types (beans)...

src/test/java/resources/ customer.yaml:
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"
src/test/java/com/windofkeltia/yaml/ Client.java:
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();
  }
}
src/test/java/com/windofkeltia/yaml/ Contact.java:
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();
  }
}
src/test/java/com/windofkeltia/yaml/ Address.java:
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();
  }
}
src/test/java/com/windofkeltia/yaml/ SnakeYamlTest.java:

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