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

前端 未结 3 1606
情书的邮戳
情书的邮戳 2021-01-04 13:34

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:
  typ         


        
相关标签:
3条回答
  • 2021-01-04 13:41

    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)
    
    0 讨论(0)
  • 2021-01-04 13:54

    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);
    
    0 讨论(0)
  • 2021-01-04 13:58

    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;
    }
    
    0 讨论(0)
提交回复
热议问题