Рубрики
Без рубрики

Разбор YAML с помощью SnakeYAML

Обзор разбора простых и не очень простых документов YAML в Java-объекты – и наоборот – с помощью библиотеки SnakeYAML.

Автор оригинала: baeldung.

1. Обзор

В этом учебнике мы узнаем, как использовать ЗмеяЯМЛ библиотека к сериализации java-объектов к документам YAML и наоборот .

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

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


    org.yaml
    snakeyaml
    1.21            

3. Точка входа

Ямл класс является точкой входа для API:

Yaml yaml = new Yaml();

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

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

Библиотека обеспечивает поддержку загрузки документа из Струнные или ВходНая трансляция . Большинство образцов кода здесь будет основано на анализе ВходНая трансляция .

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

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

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

Теперь мы будем разбирать выше 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}

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

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

Библиотека также предоставляет способ загрузки документа в качестве пользовательского класса . Эта опция позволит легко пройти данные в памяти.

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

public class Customer {

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

    // getters and setters
}

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

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

!!com.baeldung.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);

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

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

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

Теперь о загрузке customer.yaml, Мы получим Клиентская объект:

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

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

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

Например:

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. Вложенные предметы и коллекции

Учитывая тип верхнего уровня, библиотека автоматически обнаруживает типы вложенных объектов , если они не интерфейс или абстрактный класс, и deserializes документ в соответствующий вложенный тип.

Давайте добавим Контактные и Адрес детали к 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

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

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

Посмотрим, как Контактные и Адрес классы выглядят так:

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
}

Теперь мы будем тестировать Ямл # нагрузки () с учетом тестового случая:

@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 являются безопасными для типов (общими) коллекциями, важно указать ТипВзрачение чтобы был идентифицирован правильный параметризированный тип.

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

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

Для загрузки этого документа мы можем указать ТипВзрачение для данного свойства на верхнем уровне класса :

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

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

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

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

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

Библиотека также предоставляет метод сбросить данный объект Java в документ YAML . Выход может быть Струнные или указанный файл/поток.

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

Начнем с простого примера сброса экземпляра Карта<Стринг, объект> к документу YAML ( Струнные ):

@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

Мы также можем выбрать сброс пользовательских типов Java в выходный поток . Это, однако, добавит глобальный явный тег к выходной документу:

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

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

С помощью вышеуказанного подхода мы по-прежнему сбрасываем информацию о теге в документе YAML.

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

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

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

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

Эта статья иллюстрирует использование библиотеки SnakeYAML для сериализации объектов Java для YAML и наоборот.

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