yamlsnakeyaml

How to read a single nested property from YAML using Java SnakeYaml?


I'm a bit confused, how do I quickly retrieve a property from a loaded Yaml SnakeYAML object? I shouldn't have to traverse through maps etc., right?

e.g.

app:
   ftp:
     host: 1.1.1.1

I read in my file

    Yaml yaml = new Yaml();
    InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("application-test.yaml");
    Map<String, Object> obj = yaml.load(inputStream);
    inputStream.close();

and now I should be able to quickly do something like

obj.get("app").get("ftp").get("host") or yaml.get("app.ftp.host")

right? But there are errors: Something has to be cast to a Linked Hash Map, or there has to be iteration of fields, etc. I don't want to iterate anything myself, I want to quickly get a nested property with some kind of getter.

Why do I have to do this craziness to get a nested property even with the SnakeYaml library added to the project?

Yaml yaml = new Yaml();
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("application-test.yaml");
Map<String, Object> obj = yaml.load(inputStream);
inputStream.close();

LinkedHashMap objLevel1 = (LinkedHashMap)obj.get("app");
LinkedHashMap objLevel2 = (LinkedHashMap)objLevel1.get("sftp");
Integer port = (Integer)objLevel2.get("port");

Solution

  • Define the layout of the data in your file with classes:

    class Ftp {
        public String host;
        public int port;
    }
    
    class App {
        public Ftp ftp;
    }
    
    class Data {
        public App app;
    }
    

    Then you can do

    Data data = yaml.loadAs(inputStream, Data.class);
    String host = data.app.ftp.host;
    int port = data.app.ftp.port;
    

    If you don't want to do that, you can always write a getter yourself:

    <T> T getFromYaml(InputStream input, String[] path, Class<T> type) {
        Yaml yaml = new Yaml();
        Object data = yaml.load(inputStream);
        for (String item: path) {
            Map map = (Map) data;
            data = map.get(item);
        }
        return (T) data;
    }
    
    // and then in your loading code:
    try (InputStream inputStream = getClass().getResourceAsStream("/application-test.yaml")) {
        Integer port = getFromYaml(inputStream, new String[] {"app", "ftp", "port"}, Integer.class);
        // do something with it
    }
    

    The first method is preferred since SnakeYAML can yield good error messages, for example if the port value is not an integer or ftp is not a mapping. The second method will leave you alone with a casting exception (you can of course expand it to output good error messages).