使用 snakeYaml 在根部解析带有映射的 YAML 文档

Parsing a YAML document with a map at the root using snakeYaml

我想将 YAML 文档读取到自定义对象的地图(而不是 snakeYaml 默认执行的地图)。所以这个:

19:
  typeID: 2
  limit: 300
20:
  typeID: 8
  limit: 100

将加载到如下所示的地图:

Map<Integer, Item>

其中项目是:

class Item {
    private Integer typeId;
    private Integer limit;
}

我找不到使用 snakeYaml 执行此操作的方法,我也找不到更好的库来完成这项任务。

文档只有 maps/collections 嵌套在其他对象中的示例,因此您可以执行以下操作:

    TypeDescription typeDescription = new TypeDescription(ClassContainingAMap.class);
    typeDescription.putMapPropertyType("propertyNameOfNestedMap", Integer.class, Item.class);
    Constructor constructor = new Constructor(typeDescription);
    Yaml yaml = new Yaml(constructor);
    /* creating an input stream (is) */
    ClassContainingAMap obj = (ClassContainingAMap) yaml.load(is);

但是当地图格式位于文档的根目录时,我该如何定义它呢?

您需要添加自定义 Constructor。但是,在您的情况下,您不想注册 "item" 或 "item-list" 标签。

实际上,您想将 Duck Typing 应用到您的 Yaml。它不是超级高效,但有一种相对简单的方法可以做到这一点。

class YamlConstructor extends Constructor {
  @Override
  protected Object constructObject(Node node) {

    if (node.getTag() == Tag.MAP) {
        LinkedHashMap<String, Object> map = (LinkedHashMap<String, Object>) super
                .constructObject(node);
        // If the map has the typeId and limit attributes
        // return a new Item object using the values from the map
        ...
    }
     // In all other cases, use the default constructObject.
    return super.constructObject(node);

这是我在非常相似的情况下所做的。我只是将我的整个 yml 文件放在一个选项卡上,并在顶部添加了一个 map: 标签。所以对于你的情况就是这样。

map:
  19:
    typeID: 2
    limit: 300
  20:
    typeID: 8
    limit: 100

然后在您的 class 中创建一个静态 class 来读取此文件,如下所示。

static class Items {
    public Map<Integer, Item> map;
}

然后阅读您的地图只需使用。

Yaml yaml = new Yaml(new Constructor(Items));
Items items = (Items) yaml.load(<file>);
Map<Integer, Item> itemMap = items.map;

更新:

如果您不想或不能编辑您的 yml 文件,您可以在读取文件时使用类似这样的代码执行上述转换。

try (BufferedReader br = new BufferedReader(new FileReader(new File("example.yml")))) {
    StringBuilder builder = new StringBuilder("map:\n");
    String line;
    while ((line = br.readLine()) != null) {
        builder.append("    ").append(line).append("\n");
    }

    Yaml yaml = new Yaml(new Constructor(Items));
    Items items = (Items) yaml.load(builder.toString());
    Map<Integer, Item> itemMap = items.map;
}

为了保持类型安全,您需要控制根节点的构造。为此,可以使用Constructor的rootTag属性标记根节点,固定children的类型,构造map。

自定义构造函数应如下所示

public static class MyConstructor extends Constructor {
    private TypeDescription itemType = new TypeDescription(Item.class);

    public MyConstructor() {
        this.rootTag = new Tag("myRoot");
        this.addTypeDescription(itemType);
    }

    @Override
    protected Object constructObject(Node node) {
        if ("myRoot".equals(node.getTag().getValue()) && node instanceof MappingNode) {
            MappingNode mNode = (MappingNode) node;
            return mNode.getValue().stream().collect(
                    Collectors.toMap(
                            t -> super.constructObject(t.getKeyNode()),
                            t -> {
                                Node child = t.getValueNode();
                                child.setType(itemType.getType());
                                return super.constructObject(child);
                            }
                    )
            );

        } else {
            return super.constructObject(node);
        }
    }
}

这是功能齐全的示例

import org.yaml.snakeyaml.TypeDescription;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.Tag;

import java.util.Map;
import java.util.stream.Collectors;

public class Foo {

public static void main(String[] args) {
    String theYaml = "19:\n" +
            "  typeID: 2\n" +
            "  limit: 300\n" +
            "20:\n" +
            "  typeID: 8\n" +
            "  limit: 100";

    Yaml yaml = new Yaml(new MyConstructor());
    Map<String, Item> data = yaml.load(theYaml);
    System.out.println(data);
}

public static class Item {
    private int typeID, limit;

    public int getTypeID() {
        return typeID;
    }

    public void setTypeID(int typeID) {
        this.typeID = typeID;
    }

    public int getLimit() {
        return limit;
    }

    public void setLimit(int limit) {
        this.limit = limit;
    }
}

public static class MyConstructor extends Constructor {
    private TypeDescription itemType = new TypeDescription(Item.class);

    public MyConstructor() {
        this.rootTag = new Tag("myRoot");
        this.addTypeDescription(itemType);
    }

    @Override
    protected Object constructObject(Node node) {
        if ("myRoot".equals(node.getTag().getValue()) && node instanceof MappingNode) {
            MappingNode mNode = (MappingNode) node;
            return mNode.getValue().stream().collect(
                    Collectors.toMap(
                            t -> super.constructObject(t.getKeyNode()),
                            t -> {
                                Node child = t.getValueNode();
                                child.setType(itemType.getType());
                                return super.constructObject(child);
                            }
                    )
            );

        } else {
            return super.constructObject(node);
        }
    }
}
}

如果你不关心类型,只想鸭子打字(一切都是地图),你可以在没有设置的情况下加载。

Java

Map<String, Object> data = new Yaml().load(yamldata);

斯卡拉

val data: java.util.Map[String, Any] = new Yaml().load(yamlData)

从Groovy 3开始,您可以使用Groovy解析YAML。

import groovy.yaml.YamlSlurper

...

def yaml = new YamlSlurper().parseText(text)

如果你想将它映射到你的自定义类型,你可以这样做。

yaml.collect({key, item -> new Item(typeId: item?.typeID, limit: item?.limit)})

我意识到 Groovy 中对 YAML 的这种支持自 2019 年以来才可用。但现在 Groovy 确实为基于 JVM 的系统提供了一个简单的选项(即它可以混合使用与 Java), 我认为值得为其他可能遇到类似问题的人发帖。