Parsing a YAML document with a map at the root usi

2019-01-25 12:50发布

问题:

I want to read a YAML document to a map of custom objects (instead of maps, which snakeYaml does by default). So this:

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

Would be loaded to a map which looks like this:

Map<Integer, Item>

where Item is:

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

I could not find a way to do this with snakeYaml, and I couldn't find a better library for the task either.

The documentation only has examples with maps/collections nested inside other objects, so that you can do the following:

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

But how do I go about defining the Map format when it is at the root of the document?

回答1:

You need to add a custom Constructor. However, in your case you don't want register an "item" or "item-list" tag.

In effect, you want to apply Duck Typing to your Yaml. It's not super efficient, but there is a relatively easy way to do this.

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


回答2:

Here is what I did for a very similar situation. I just tabbed my whole yml file over one tab and added a map: tag to the top. So for your case it would be.

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

And then create a static class in your class that reads this file like follows.

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

And then to read your map just use.

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

UPDATE:

If you don't want to or cannot edit your yml file you could just do the above transform in code while reading the file with something like this.

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


回答3:

yaml.load returns a map. You can just do this:

Java Version

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

Scala Version

val yaml = new Yaml()
yaml.load(getClass.getResourceAsStream(name))
    .asInstanceOf[java.util.Map[String, Any]]