Parsing into Map<String, List<...>>

340 views
Skip to first unread message

Richard Pavlicek

unread,
Mar 28, 2021, 1:17:12 AM3/28/21
to SnakeYAML
I was looking at one of the example YAML files (map-bean-10.yaml):

data:
  aaa: 1
  bbb: 2
  zzz: 3
developers:
  team1:
    name: Fred
    role: creator
  team2:
    name: John
    role: committer
name: Bean123

And this parses fine with these two beans in TypeSafeMapTest.java:

    public static class MapBean {
        private Map<String, Integer> data;
        private String name;
        private Map<String, Developer2> developers;
...
}

    public static class Developer2 {
        private String name;
        private String role;
...
}

However I wish to accomplish something like this instead:

data:
  aaa: 1
  bbb: 2
  zzz: 3
developers:
  team1:
    - name: Fred
      role: creator
    - name: John
      role: committer
  team2:
    - name: John
      role: committer
name: Bean123

Where each "variable"  key (team1 and team2) points to a list (collection) of Developer2 types instead of only containing a single instance..

I created "MapBeanList" for this purpose:

    public static class MapBeanList {
        private Map<String, Integer> data;
        private String name;
        private Map<String, List<Developer2>> developers;
...
}

And ran this code to load it:

Yaml beanLoader = new Yaml();
MapBeanList parsed = beanLoader.loadAs(output, MapBeanList.class);


But instead of the list in team1/2 containing "Developer2" types, it contains a generic LinkedHashMap.

Is there something additional I need to do? Do I need to create a Constructor or TypeDescription? Or is this not even possible?

Thanks!
-Rich


maslovalex

unread,
Mar 28, 2021, 4:05:02 PM3/28/21
to SnakeYAML
Hi Rich,

Unfortunately there is no easy (general) way to do it, yet.

This is what you can try:

Yaml beanLoader = new Yaml();
TypeDescription mb2 = new TypeDescription(MapBean2.class) {

    @Override
    public boolean setupPropertyType(String key, org.yaml.snakeyaml.nodes.Node valueNode) {
        if ("developers".equals(key) &&  valueNode instanceof MappingNode) {
            MappingNode mp = (MappingNode) valueNode;
            for (NodeTuple nodeTuple : mp.getValue()) {
                if (nodeTuple.getValueNode() instanceof SequenceNode) {
                    SequenceNode sn = (SequenceNode) nodeTuple.getValueNode();
                    sn.setListType(Developer2.class);
                    return true;
                }
            }
        }
        return super.setupPropertyType(key, valueNode);
    };
};

beanLoader.addTypeDescription(mb2);

MapBean2 parsed = beanLoader.loadAs(output, MapBeanList.class);

Best regards,
 Alex

Richard Pavlicek

unread,
Mar 29, 2021, 4:05:28 PM3/29/21
to SnakeYAML
Alex,

It works! Thank you so much :D

I noticed that this wouldn't work with older snakeyaml source code (not sure which revision). So then I tested this on tag 1.28, and it works fine. 

Do you know if there is a "todo-item" to allow this kind of parsing based on the bean class to work automatically without requiring a special "fix"? If so, is there a projected release for it?

Either way your help is greatly appreciated!

Cheers,
-Rich

Richard Pavlicek

unread,
Mar 29, 2021, 6:32:34 PM3/29/21
to SnakeYAML
FYI, I had to modify your solution since my YAML structure was slightly different, but the concept was similar so it wasn't that hard to modify it after debugging.

I'm posting this for the benefit of others searching for solutions in case they need to know...

Yaml:

data:
  aaa: 1
  bbb: 2
  zzz: 3
developers:
  teams:
    team1:
      - name: Fred
          role: creator
      - name: John
        role: committer
    team2:
      - name: John
        role: committer
  other:
    key: val
name: Bean123


Java:

        TypeDescription developersDesc = new TypeDescription(MapBean2.class) {

            @Override
            public boolean setupPropertyType(String key, org.yaml.snakeyaml.nodes.Node valueNode) {
                if ("developers".equals(key) && valueNode instanceof MappingNode) {
                    MappingNode mp = (MappingNode) valueNode;
                    for (NodeTuple nodeTuple : mp.getValue()) {
                        ScalarNode scalar = ((ScalarNode) nodeTuple.getKeyNode());
                        if ("teams".equals(scalar.getValue()) && nodeTuple.getValueNode() instanceof MappingNode) {
                            mp = (MappingNode) nodeTuple.getValueNode();
                            for (NodeTuple innerNode : mp.getValue()) {
                                if (innerNode.getValueNode() instanceof SequenceNode) {
                                    SequenceNode sn = (SequenceNode) innerNode.getValueNode();
                                    sn.setListType(Developer2.class);
                                    return true;
                                }
                            }

                        }
                    }
                }
                return super.setupPropertyType(key, valueNode);
            };
        };
        
        beanLoader.addTypeDescription(developersDesc);


Cheers,
-Rich
Reply all
Reply to author
Forward
0 new messages