Search code examples
groovyyamlsnakeyaml

SnakeYAML by example


I am trying to read and parse a YAML file with SnakeYAML and turn it into a config POJO for my Java app:

// Groovy pseudo-code
class MyAppConfig {
    List<Widget> widgets
    String uuid
    boolean isActive

    // Ex: MyAppConfig cfg = new MyAppConfig('/opt/myapp/config.yaml')
    MyAppConfig(String configFileUri) {
        this(loadMap(configFileUri))
    }

    private static HashMap<String,HashMap<String,String>> loadConfig(String configFileUri) {
        Yaml yaml = new Yaml();
        HashMap<String,HashMap<String,String>> values
        try {
            File configFile = Paths.get(ClassLoader.getSystemResource(configUri).toURI()).toFile();
            values = (HashMap<String,HashMap<String,String>>)yaml.load(new FileInputStream(configFile));
        } catch(FileNotFoundException | URISyntaxException ex) {
            throw new MyAppException(ex.getMessage(), ex);
        }

        values
    }

    MyAppConfig(HashMap<String,HashMap<String,String>> yamlMap) {
        super()

        // Here I want to extract keys from 'yamlMap' and use their values
        // to populate MyAppConfig's properties (widgets, uuid, isActive, etc.).
    }
}

Example YAML:

widgets:
  - widget1
    name: blah
    age: 3000
    isSilly: true
  - widget2
    name: blah meh
    age: 13939
    isSilly: false
uuid: 1938484
isActive: false

Since it appears that SnakeYAML only gives me a HashMap<String,<HashMap<String,String>> to represent my config data, it seems as though we can only have 2 nested mapped properties that SnakeYAML supports (the outer map and in the inner map of type <String,String>)...

  1. But what if widgets contains a list/sequence (say, fizzes) which contained a list of, say, buzzes, which contained yet another list, etc? Is this simply a limitation of SnakeYAML or am I using the API incorrectly?
  2. To extract values out of this map, I need to iterate its keys/values and (seemingly) need to apply my own custom validation. Does SnakeYAML provide any APIs for doing this extraction + validation? For instance, instead of hand-rolling my own code to check to see if uuid is a property defined inside the map, it would be great if I could do something like yaml.extract('uuid'), etc. And then ditto for the subsequent validation of uuid (and any other property).
  3. YAML itself contains a lot of powerful concepts, such as anchors and references. Does SnakeYAML handle these concepts? What if an end user uses them in the config file - how am I supposed to detect/validate/enforce them?!? Does SnakeYAML provide an API for doing this?

Solution

  • Do you mean like this:

    @Grab('org.yaml:snakeyaml:1.17')
    import org.yaml.snakeyaml.*
    import org.yaml.snakeyaml.constructor.*
    import groovy.transform.*
    
    String exampleYaml = '''widgets:
                           |  - name: blah
                           |    age: 3000
                           |    silly: true
                           |  - name: blah meh
                           |    age: 13939
                           |    silly: false
                           |uuid: 1938484
                           |isActive: false'''.stripMargin()
    
    @ToString(includeNames=true)
    class Widget {
        String name
        Integer age
        boolean silly
    }
    
    @ToString(includeNames=true)
    class MyConfig {
        List<Widget> widgets
        String uuid
        boolean isActive
    
        static MyConfig fromYaml(yaml) {
            Constructor c = new Constructor(MyConfig)
            TypeDescription t = new TypeDescription(MyConfig)
            t.putListPropertyType('widgts', Widget)
            c.addTypeDescription(t);
    
            new Yaml(c).load(yaml)
        }
    }
    
    println MyConfig.fromYaml(exampleYaml)
    

    Obviously, that's a script to run in the Groovy console, you wouldn't need the @Grab line, as you probably already have the library in your classpath ;-)