Поиск значения во вложенных данных YAML

В настоящее время я пытаюсь проанализировать файл YAML в качестве ввода/конфигурации для запуска некоторых тестов. Проблема в том, что при использовании Джексона данные вложения, похоже, не вписываются в класс, независимо от структуры, которую я для него разрабатываю, почти каждый раз, когда я получаю что-то вроде этого:

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.ArrayList` out of START_OBJECT token

Я намерен просто «искать» данные в файле YAML, используя аналогичный подход XPath, не беспокоясь о сопоставлении объектов и конечных уровнях вложенности.

Вот пример класса:

public class YAMLInput {

    private ArrayList<SomeContainer> containers;
    //getter and setters

    private class SomeContainer {
        private String name; 
        private String path;
        private ArrayList<Integer> intList;
        private ArrayList<String> strList;
        private ArrayList<SomeObject> someObjList;

        private class SomeObject {
             private String objectName;
             private ArrayList<String> strList;
        }

    }
}

И ввод Yaml:

container:
    name: Cont1
    path: /storage/outputFolder
    intList: 
        - 100
        - 200
        - 300
    strList:
        - strFirst
        - strSecond
        - strThird
    someObjList: 
        obj1: 
          objName: strname
          strList: 
             - 100
             - 200
             - 300
        obj2:
          # (...)

Идея состоит в том, чтобы построить конструктор для класса YAMLInput:

public YAMLInput( SearchableYAMLData data) {
   for(SearchableYAMLData container : data.getList("container")){
      this.containers.add( new SomeContainer());
      this.containers.get(0) = container.get("name");
      //...
   }
}

Какой инструмент будет ближе всего к этому гипотетическому классу SearchableYAMLData?


person Evandro Teixeira    schedule 08.03.2019    source источник


Ответы (2)


Ошибка, которую вы получаете, вероятно, связана с тем, что отображаемый вами YAML не соответствует классу, который вы показываете. someObjList в ваших данных YAML — это сопоставление (содержит пары ключ-значение, где первый ключ — obj1), а в вашем классе — это ArrayList<SomeObject>. Это соответствует последовательности в ваших данных YAML и должно выглядеть так:

someObjList: 
    - objName: strname
      strList: 
         - 100
         - 200
         - 300
    - # (...)

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

При этом, если вы ищете способ поиска в произвольном YAML, не используйте Jackson. Jackson — это инструмент для (де)сериализации, и вы не хотите десериализовать свой YAML; вы просто хотите пройтись по его структуре. Для этого вы можете использовать SnakeYAML, парсер YAML, который использует Джексон:

Yaml yaml = new Yaml();
Node root = yaml.compose(new StringReader("foo: bar"));

root будет либо ScalarNode, либо MappingNode, либо SequenceNode. Последние два будут содержать дочерние узлы, на которые вы можете спуститься. Эта структура, безусловно, пригодна для поиска в стиле XPath.

Если вам нужна производительность, более быстрым способом будет использование последовательного интерфейса parse SnakeYaml. По сути, вы постоянно запрашиваете следующее событие из синтаксического анализатора и проверяете, содержит ли его путь, который вы ищете. Если это так, продолжайте запрашивать его дочерние элементы и ищите там следующий элемент пути. Если нет, проанализируйте и создайте дамп всего дочернего содержимого текущего события, а затем продолжите поиск текущего элемента пути.

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

person flyx    schedule 08.03.2019

Вы видите Cannot deserialize instance of "java.util.ArrayList" out of START_OBJECT token, потому что на корневом уровне вы определили ArrayList<SomeContainer> containers, но файл YAML содержит object. Чтобы избежать этого, нам нужно настроить ObjectMapper для приема одного объекта, такого как array:

ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);

Кроме того, obj1 и obj2 не определены в вашей модели. Поэтому вы должны удалить их или создать дополнительные объекты-оболочки. Но если вам нужно только просмотреть файл YAML, вы можете прочитать его как файл Map. Ниже код:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;

import java.io.File;
import java.util.Map;

public class YamlApp {

    public static void main(String[] args) throws Exception {
        File yamlFile = new File("./resource/test.yaml").getAbsoluteFile();

        ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
        Map yaml = mapper.readValue(yamlFile, Map.class);

        System.out.println(yaml);
    }
}

печатает:

{container={name=Cont1, path=/storage/outputFolder, intList=[100, 200, 300], strList=[strFirst, strSecond, strThird], someObjList={obj1={objName=strname, strList=[100, 200, 300]}, obj2={objName=strname2, strList=[1002, 2002, 3002]}}}}
person Michał Ziober    schedule 08.03.2019