Разбор YAML со SnakeYAML

Разбор YAML со SnakeYAML

1. обзор

В этом руководстве мы узнаем, как использовать библиотекуSnakeYAML дляserialize Java objects to YAML documents and vice versa.

2. Настройка проекта

Чтобы использовать SnakeYAML в нашем проекте, мы добавим следующую зависимость Maven (последнюю версию можно найтиhere):


    org.yaml
    snakeyaml
    1.21

3. Входная точка

КлассYaml - это точка входа в API:

Yaml yaml = new Yaml();

Поскольку реализация не является потокобезопасной, разные потоки должны иметь свой собственный экземплярYaml.

4. Загрузка документа YAML

Библиотека обеспечивает поддержку загрузки документа изString илиInputStream. Большинство примеров кода здесь будут основаны на анализеInputStream.

Начнем с определения простого документа YAML и присвоения имени файлуcustomer.yaml:

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

4.1. Основное использование

Теперь мы проанализируем указанный выше YAML-документ с помощью классаYaml:

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

Приведенный выше код генерирует следующий вывод:

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

По умолчанию методload() возвращает экземплярMap. Запросы к объектуMap каждый раз потребовали бы, чтобы мы знали имена ключей свойств заранее, а также нелегко перемещаться по вложенным свойствам.

4.2. Пользовательский Тип

Библиотека такжеprovides a way to load the document as a custom class. Эта опция позволит легко просматривать данные в памяти.

Давайте определим классCustomer и попробуем загрузить документ снова:

public class Customer {

    private String firstName;
    private String lastName;
    private int age;

    // getters and setters
}

Предполагая, что документ YAML десериализован как известный тип, мы можем указать явный глобальныйtag в документе.

Давайте обновим документ и сохраним его в новом файлеcustomer_with_type.yaml:

!!com.example.snakeyaml.Customer
firstName: "John"
lastName: "Doe"
age: 20

Обратите внимание на первую строку в документе, которая содержит информацию о классе, который будет использоваться при его загрузке.

Теперь мы обновим код, использованный выше, и передадим новое имя файла в качестве входных данных:

Yaml yaml = new Yaml();
InputStream inputStream = this.getClass()
 .getClassLoader()
 .getResourceAsStream("yaml/customer_with_type.yaml");
Customer customer = yaml.load(inputStream);

Методload() теперь возвращает экземплярCustomer типа * Недостатком этого подхода является то, что тип должен быть экспортирован как библиотека, чтобы его можно было использовать там, где это необходимо *.

Хотя мы могли бы использовать явный локальный тег, для которого мы не обязаны экспортировать библиотеки.

Another way of loading a custom type is by using the Constructor class. Таким образом, мы можем указать корневой тип для документа YAML, который будет проанализирован. Давайте создадим экземплярConstructor с типомCustomer в качестве корневого типа и передадим его экземпляруYaml.

Теперь при загрузкеcustomer.yaml, we’re вы получите объектCustomer:

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

4.3. Неявные типы

In case there’s no type defined for a given property, the library automatically converts the value to an implicit type.

Например:

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

Давайте проверим это неявное преобразование типа с помощью тестового примера:

@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. Вложенные объекты и коллекции

Given a top-level type, the library automatically detects the types of nested objects, если они не являются интерфейсом или абстрактным классом, и десериализует документ в соответствующий вложенный тип.

Давайте добавимContact иAddress details кcustomer.yaml, и сохраним новый файл какcustomer_with_contact_details_and_address.yaml. 

Теперь разберем новый документ 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

КлассCustomer также должен отражать эти изменения. Вот обновленный класс:

public class Customer {
    private String firstName;
    private String lastName;
    private int age;
    private List contactDetails;
    private Address homeAddress;
    // getters and setters
}

Давайте посмотрим, как выглядят классыContact и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
}

Теперь мы протестируемYaml #load() с заданным тестовым примером:

@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. Типовые коллекции

Если одно или несколько свойств данного Java-класса являются типобезопасными (универсальными) коллекциями, то важно указатьTypeDescription, чтобы идентифицировать правильный параметризованный тип.

Давайте возьмем одинCustomer, имеющий более одногоContact, и попробуем загрузить его:

firstName: "John"
lastName: "Doe"
age: 31
contactDetails:
   - { type: "mobile", number: 123456789}
   - { type: "landline", number: 123456789}

Чтобы загрузить этот документ,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. Загрузка нескольких документов

Могут быть случаи, когда в одномFile есть несколько документов YAML, и мы хотим проанализировать их все. КлассYaml предоставляет методloadAll() для выполнения такого анализа.

По умолчанию метод возвращает экземплярIterable<Object>, где каждый объект имеет типMap<String, Object>. . Если требуется настраиваемый тип, мы можем использовать экземплярConstructor, как описано выше

Рассмотрим следующие документы в одном файле:

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

Мы можем проанализировать приведенное выше, используя методloadAll(), как показано в приведенном ниже примере кода:

@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. Дамп документов YAML

Библиотека также предоставляет методdump a given Java object into a YAML document. Результатом может бытьString или указанный файл / поток.

5.1. Основное использование

Мы начнем с простого примера сохранения экземпляраMap<String, Object> в документ YAML (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());
}

Приведенный выше код дает следующий результат (обратите внимание, что использование экземпляраLinkedHashMap сохраняет порядок выходных данных):

name: Silenthand Olleander
race: Human
traits: [ONE_HAND, ONE_EYE]

5.2. Пользовательские объекты Java

Мы также можем выбратьdump custom Java types into an output stream. Однако это добавит глобальный явныйtag в выходной документ:

@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.example.snakeyaml.Customer {age: 45, contactDetails: null, firstName: Greg,\n  homeAddress: null, lastName: McDowell}\n";

    assertEquals(expectedYaml, writer.toString());
}

При использовании описанного выше подхода мы все еще сохраняем информацию о тегах в документе YAML.

Это означает, что мы должны экспортировать наш класс как библиотеку для любого потребителя, который десериализует его. Чтобы избежать имени тега в выходном файле, мы можем использовать методdumpAs(), предоставляемый библиотекой.

Таким образом, в приведенном выше коде мы могли бы настроить следующее, чтобы удалить тег:

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

6. Заключение

В этой статье показано использование библиотеки SnakeYAML для сериализации объектов Java в YAML и наоборот.

Все примеры можно найти вthe GitHub project - это проект на основе Maven, поэтому его должно быть легко импортировать и запускать как есть.