在 YAML 嵌套数据中搜索值

Search for value within YAML nested data

我目前正在尝试将 YAML 文件解析为 input/configuration 以用于 运行 一些测试。问题是:使用 Jackson,无论我为它设计的结构如何,嵌套数据似乎都不适合 class,几乎每次我得到这样的东西时:

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

我打算使用类似的 XPath 方法简单地 "search" 用于 YAML 文件中的数据,而不用担心映射对象和有限的嵌套级别。

示例如下 class:

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 class 构建构造函数:

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

最接近这个假设的可用工具是什么 SearchableYAMLData class?

您得到的错误可能源于您显示的 YAML 与您显示的 class 不对应。 YAML 数据中的 someObjList 是一个映射(包含 key-value 对,第一个键是 obj1),而在你的 class 中,它是一个 ArrayList<SomeObject>。这对应于 YAML 数据中的一个序列,应该如下所示:

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

但是,我不确定,因为您并没有真正显示产生错误的代码。

也就是说,如果您正在寻找一种通过任意 YAML 进行搜索的方法,请不要使用 Jackson。 Jackson 是一个用于(反)序列化的工具,您不想反序列化您的 YAML;你只想走它的结构。为此,您可以使用 Jackson 使用的 YAML 解析器 SnakeYAML:

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

root 将是 ScalarNodeMappingNodeSequenceNode。后两个将包含 child 个可以下降的节点。这种结构对于XPath-like seaching当然是可行的

如果您追求性能,更快的方法是使用 SnakeYaml 的顺序 parse 接口。基本上,您不断地从解析器查询下一个事件,并检查您正在搜索的路径是否包含它。如果是,继续查询它的 children 并在那里搜索路径中的下一个元素。如果不是,则解析并转储当前事件的所有 child 内容,然后继续搜索当前路径元素。

如果你能阅读 Python,你可以从 中获得一些灵感,它通过事件解析输入 YAML,你可以指定要附加数据的路径。

您看到 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);

此外,obj1obj2 未在您的模型中定义。所以你应该删除它们或创建额外的包装器对象。但是如果你只需要遍历 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]}}}}