web-dev-qa-db-ja.com

snakeYamlを使用してルートにマップがあるYAMLドキュメントを解析する

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

しかし、ドキュメントのルートにあるときにマップ形式を定義するにはどうすればよいですか?

9
Justas

カスタムを追加する必要があります コンストラクター 。ただし、あなたの場合、「item」または「item-list」タグを登録したくありません。

実際には、Yamlに Duck Typing を適用する必要があります。これはあまり効率的ではありませんが、これを行うには比較的簡単な方法があります。

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);
4
Emily Crutcher

これが私が非常に似た状況でしたことです。 ymlファイル全体を1つのタブにタブで移動し、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;
}
8
crowmagnumb

型の安全性を維持するには、ルートノードの構築を制御する必要があります。これを行うには、コンストラクターのrootTagプロパティを使用して、ルートノードをマークし、子のタイプを修正して、マップを作成します。

カスタムコンストラクタは次のようになります

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

Scala

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