在YAML嵌套数据中搜索值

时间:2019-03-08 13:50:01

标签: java xpath jackson yaml snakeyaml

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

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

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

这是示例类:

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类最接近的可用工具是什么?

2 个答案:

答案 0 :(得分:3)

您收到的错误可能是由于您显示的YAML与您显示的类不对应的事实。 YAML数据中的someObjList是一个映射(包含键/值对,第一个键为obj1),而在您的课程中,它是ArrayList<SomeObject>。这对应于您的YAML数据中的序列,并且应如下所示:

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

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

话虽如此,如果您正在寻找一种搜索任意YAML的方法,请不要使用Jackson。 Jackson是用于反序列化的工具,您不想反序列化您的YAML。您只想了解其结构。为此,您可以使用SnakeYAML,这是Jackson使用的YAML解析器:

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

root将是ScalarNodeMappingNodeSequenceNode。后两个将包含您可以依赖的子节点。这种结构对于类似XPath的搜索当然是可行的。

如果您追求性能,一种更快的方法是使用SnakeYaml的顺序parse接口。基本上,您从解析器中连续查询下一个事件,并检查要搜索的路径是否包含该事件。如果是这样,请继续查询其子级并在其中的路径中搜索下一个元素。如果不是,请解析并转储当前事件的所有子内容,然后继续搜索当前路径元素。

如果您能阅读Python,则可以从my answer here中获得一些启发,该{@ 3}通过事件来解析输入的YAML,并且可以指定要在其中追加数据的路径。

答案 1 :(得分:1)

您看到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]}}}}