Lectura y escritura de archivos YAML en Java con SnakeYAML

En este tutorial, repasaremos cómo leer y escribir archivos YAML en Java con SnakeYAML, una alternativa popular a Jackson.

Introducción

YAML significa YAML Ain't Markup Language, es un lenguaje de serialización de datos que se usa más comúnmente para especificar los detalles de configuración del proyecto. La principal motivación detrás de YAML es que está diseñado para estar en un formato amigable para los humanos. Con un vistazo podemos obtener una comprensión de las propiedades y sus respectivos valores, y también la relación entre las propiedades si existe.

Como los archivos YAML ahora se usan con frecuencia, casi en todos los demás proyectos nos encontramos con un escenario en el que tenemos que administrar datos en archivos YAML a través de nuestro código. Hay muchas bibliotecas de código abierto disponibles para manejar archivos YAML en Java.

Para lograr esto, podemos usar cualquiera de las dos bibliotecas populares: jackson o [SerpienteYAML](https://bitbucket.org /asomov/snakeyaml/wiki/Inicio).

En este artículo, nos centraremos en Cómo leer y escribir archivos YAML en Java con SnakeYAML.

SerpienteYAML

SerpienteYAML es una biblioteca de análisis YAML con una API de alto nivel para la serialización y deserialización de documentos YAML.

El punto de entrada para SnakeYAML es la clase Yaml, similar a cómo la clase ObjectMapper es el punto de entrada en Jackson.

La carga de documentos se puede realizar para documentos individuales a través del método load(), o por lotes mediante el método loadAll(). Los métodos aceptan un InputStream, que es un formato común para encontrar archivos, así como objetos String que contienen datos YAML válidos.

Por otro lado, podemos volcar() objetos Java en documentos YAML con facilidad, donde las claves/campos y valores se asignan a un documento.

Naturalmente, SnakeYAML funciona bien con Mapas Java, dada la estructura <clave>:<valor>, sin embargo, también puede trabajar con objetos personalizados de Java .

Si está utilizando Maven, instale SnakeYAML agregando la siguiente dependencia:

1
2
3
4
5
<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>${org.snakeyaml.version}</version>
</dependency>

Y si estás usando Gradle, instalar SnakeYAML es tan simple como incluir lo siguiente en tu archivo de Gradle:

1
compile group: 'org.yaml', name: 'snakeyaml', version: '{version}'

Puede consultar la última versión de la biblioteca en Repositorio central de Maven.

Leer YAML con SnakeYAML

SnakeYAML le permite leer un archivo YAML en un simple objeto Map o analizar el archivo y convertirlo en un objeto Java personalizado. Dependiendo de sus requisitos, puede decidir en qué formato desea leer sus archivos YAML. Echemos un vistazo a ambos enfoques.

Leer archivo YAML como mapa en Java

Comencemos leyendo un archivo YAML simple como un conjunto de pares clave-valor. El archivo que estaremos leyendo tendrá los siguientes datos:

1
2
3
4
5
id: 20
name: Bruce
year: 2020
address: Gotham City
department: Computer Science

Supongamos que tenemos este YAML en la carpeta de recursos de nuestro proyecto Java. Carguemos primero el archivo como InputStream.

Luego, construiremos la instancia Yaml, que es el punto de entrada para usar la biblioteca. La instancia Yaml nos presenta métodos, como load() que nos permiten leer y analizar cualquier InputStream, Reader o String con datos YAML válidos:

1
2
3
4
5
InputStream inputStream = new FileInputStream(new File("student.yml"));

Yaml yaml = new Yaml();
Map<String, Object> data = yaml.load(inputStream);
System.out.println(data);

El método devuelve un ‘mapa’ de Java en el que el nombre de las propiedades se utilizan como claves frente a sus respectivos valores.

Tenga en cuenta que los valores en el Mapa son del tipo Objeto, porque en un archivo YAML podemos tener nuestros datos como valores de cadena, números o incluso colecciones. Todos estos pueden encajar en un ‘Objeto’ para que abarque cualquier valor que podamos poner.

Si imprimimos nuestro objeto data en el que hemos cargado el archivo YAML obtendremos el siguiente resultado:

1
{id=20, name=Bruce, year=2020, address=Gotham City, department=Computer Science}

Como puede ver, las propiedades del archivo YAML se asignan de manera simple como pares clave-valor en un objeto Map de Java.

Actualicemos nuestro archivo YAML para que también contenga datos de recopilación. El archivo de actualización YAML se ve así:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
id: 20
name: Bruce
year: 2020
address: Gotham City
department: Computer Science
courses:
  - name: Algorithms
    credits: 6
  - name: Data Structures
    credits: 5
  - name: Design Patterns
    credits: 3

Ahora nuestro archivo YAML contiene una colección de cursos que tiene múltiples valores de datos.

Para leer el archivo YAML actualizado no es necesario actualizar nuestro código Java. Nuestro código anterior podrá cargar con éxito el archivo YAML en nuestro objeto Map. Después de leer el archivo, el resultado será:

1
2
3
4
{
 id=20, name=Bruce, year=2020, address=Gotham City, department=Computer Science, 
 courses=[{name=Algorithms, credits=6}, {name=Data Structures, credits=5}, {name=Design Patterns, credits=3}]
}

El elemento de cursos en el archivo YAML se lee como una ArrayList donde cada valor en la lista es un objeto Map en sí mismo.

Leer objeto YAML como objeto Java personalizado

Ahora que hemos consumido con éxito el archivo YAML en nuestro código Java como simples pares clave-valor, carguemos el mismo archivo como un objeto Java personalizado, que es un caso de uso mucho más común.

Usaremos las siguientes clases de Java para cargar datos desde nuestros archivos YAML:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public class Person {
    private long id;
    private String name;
    private String address;
    // Getters and setters
}

public class Student extends Person {
    private int year;
    private String department;
    private List<Course> courses;
    // Getters and setters
}

public class Course {
    private String name;
    private double credits;
    // Getters and setters
}

Cargaremos los datos en un objeto ‘Estudiante’, donde el elemento de cursos del archivo YAML se convertirá en una ‘Lista’ de tipo ‘Curso’.

Usaremos el mismo archivo YAML que hemos usado en el ejemplo anterior y lo cargaremos como InputStream:

1
2
3
4
InputStream inputStream = new FileInputStream(new File("student_with_courses.yml"));
Yaml yaml = new Yaml(new Constructor(Student.class));
Student data = yaml.load(inputStream);
System.out.println(data);

Ahora, cuando estamos creando nuestro objeto de clase Yaml, estamos especificando el tipo de datos en el que queremos convertir los datos. El nuevo Constructor (Estudiante.clase) le dice a SnakeYAML que lea los datos del archivo YAML y los asigne a nuestro objeto Estudiante.

El mapeo es sencillo y los nombres de los atributos de su objeto tendrán que coincidir con los nombres de los atributos YAML (cursos -> cursos).

Esto resulta en:

1
Student[Person[id=20, name='Bruce', address='Gotham City'], year=2020, department='Computer Science', courses=[Course[name='Algorithms', credits=6.0], Course[name='Data Structure', credits=5.0], Course[name='Design patters', credits=3.0]]]

Como puede ver, SnakeYAML ha creado con éxito el objeto ‘Estudiante’ mientras mantiene intacta la herencia de la clase ‘Estudiante’ (Clase principal ‘Persona’) y la asociación con la clase ‘Curso’.

Escribir YAML con SnakeYAML

Ahora que hemos leído con éxito los archivos YAML en nuestro código Java, comencemos a escribir datos en archivos YAML usando nuestro proyecto Java. Similar a la lectura de documentos YAML, podemos escribir un Map Java simple y un objeto Java personalizado en un archivo YAML.

Escribir mapa en YAML

Primero escribamos un objeto ‘Map’ simple en un archivo YAML:

1
2
3
4
5
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("id", 19);
dataMap.put("name", "John");
dataMap.put("address", "Star City");
dataMap.put("department", "Medical");

Ahora, vamos a crear un nuevo objeto PrintWriter, con el directorio de salida en mente, y dump() el dataMap usando ese escritor.

Nota: El método dump() acepta cualquier Escritor:

1
2
3
PrintWriter writer = new PrintWriter(new File("./src/main/resources/student_output.yml"));
Yaml yaml = new Yaml();
yaml.dump(dataMap, writer);

Esto da como resultado un archivo que contiene:

1
{address: Star City, name: John, id: 19, department: Medical}

Nota: es que el archivo YAML de salida no tiene los valores en la misma secuencia en la que los agregamos en nuestro objeto Map de Java, ya que hemos usado un HashMap que no conserva el orden de entrada.

Puede solucionar este problema utilizando un LinkedHashMap en su lugar.

Escribe un objeto Java personalizado en YAML

Ahora intentemos guardar nuestra clase Student en formato YAML en el archivo de salida. Para esto, usaremos el siguiente código para configurar el objeto ‘Estudiante’:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
Student student = new Student();

student.setId(21);
student.setName("Tim");
student.setAddress("Night City");
student.setYear(2077);
student.setDepartment("Cyberware");

Course courseOne = new Course();
courseOne.setName("Intelligence");
courseOne.setCredits(5);

Course courseTwo = new Course();
courseTwo.setName("Crafting");
courseTwo.setCredits(2);

List<Course> courseList = new ArrayList<>();
courseList.add(courseOne);
courseList.add(courseTwo);

student.setCourses(courseList);

Ahora, usemos nuestra instancia Yaml con una implementación Writer para volcar() los datos en un archivo:

1
2
3
PrintWriter writer = new PrintWriter(new File("./src/main/resources/student_output_bean.yml"));
Yaml yaml = new Yaml();
yaml.dump(student, writer);

Esto resulta en:

1
2
3
4
5
6
7
8
9
!!model.Student
address: Night City
courses:
- {credits: 5.0, name: Intelligence}
- {credits: 2.0, name: Crafting}
department: Cyberware
id: 21
name: Tim
year: 2077

Si observa más de cerca los archivos de salida YAML generados por nuestro código, verá que en el primer ejemplo, todos los datos se volcaron en una sola línea, mientras que en el segundo ejemplo, los valores del objeto “Curso” se escriben en una sola línea. alinee cada uno debajo del elemento de cursos.

Aunque ambos archivos de salida generados tienen una sintaxis YAML válida, si desea crear un archivo YAML en el formato más utilizado donde cada valor se escribe en una sola línea y no hay paréntesis, puede modificar el objeto DumperOptions y pásalo al constructor Yaml:

1
2
3
4
5
DumperOptions options = new DumperOptions();
options.setIndent(2);
options.setPrettyFlow(true);
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
Yaml yaml = new Yaml(options);

Aquí, hemos especificado la sangría y el flujo del documento YAML usando el objeto DumperOptions. Ahora, cuando usamos la función dump en la instancia Yaml obtendremos una salida con un formato diferente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
!!model.Student
address: Night City
courses:
- credits: 5.0
  name: Intelligence
- credits: 2.0
  name: Crafting
department: Cyberware
id: 21
name: Tim
year: 2077

Conclusión

A medida que los archivos YAML se usan con más frecuencia, especialmente para especificar propiedades de proyectos y metadatos de compilación e implementación, es cada vez más útil poder manejarlos mediante código.

A través de SnakeYAML, podemos administrar fácilmente archivos YAML en nuestro proyecto Java, y se usa una cantidad mínima de código para cargar archivos YAML en nuestro proyecto o escribir datos en archivos YAML. SnakeYAML también proporciona opciones de formato para que pueda modificar y personalizar según sus necesidades.

El código fuente del código de muestra se puede encontrar en GitHub.