10

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

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

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

Map<Integer, Item>

项目在哪里:

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

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

该文档仅包含嵌套在其他对象中的地图/集合的示例,因此您可以执行以下操作:

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

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

4

4 回答 4

12

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

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

然后在你的类中创建一个静态类来读取这个文件,如下所示。

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;
}
于 2017-03-02T20:36:42.967 回答
5

您需要添加一个自定义Constructor。但是,在您的情况下,您不想注册“项目”或“项目列表”标签。

实际上,您希望将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);
于 2015-02-16T23:15:26.567 回答
1

To keep type safety, you need to take control of the root node construction. To do this, you can use the rootTag property of the Constructor to mark the root node, fix the type of the children, and construct the map.

The custom constructor should look like the following

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);
        }
    }
}

Here's fully functional example

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);
        }
    }
}
}

If you don't care about the types, and just want duck typing (everything is a map), You can load with no settings.

Java

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

Scala

val data: java.util.Map[String, Any] = new Yaml().load(yamlData)
于 2018-11-07T22:10:19.523 回答
0

从 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 混合),我认为它值得发布给其他人可能有类似的斗争。

于 2021-08-17T20:13:39.140 回答