Search code examples
javajava-8jacksonyamlsnakeyaml

Deserialize YAML in Java using defaults read earlier in file


I have a yaml file that looks like this:

defaultSettings:
    a: 1
    b: foo
entries:
    - name: one
      settings:
          a: 2
    - name: two
      settings:
          b: bar
    - name: three

Assume I have these classes:

class Settings {
    public int a;
    public String b;
}

class Entry {
    public String name;
    public Settings settings;
}

class Config {
    public Settings defaultSettings;
    public List<Entry> entries;
}

The key is that I want to use the defaultSettings specified at the top of the file for any settings that are not specified in the individual entries. So, the entries would come out as if they had been written like this:

    - name: one
      settings:
          a: 2
          b: foo
    - name: two
      settings:
          a: 1
          b: bar
    - name: three
      settings:
          a: 1
          b: foo

Is there a nice way to do this (e.g., with Jackson, SnakeYAML, etc.)? I have complete control over the code, so I can make modifications if that leads to a nicer way of doing things.


Solution

  • I ended up doing this, which works:

    class Settings {
        public int a;
        public String b;
    
        // 0 arg constructor uses the static defaults via the copy constructor
        public Settings() {
            this(Config.defaultSettings);
        }
    
        // copy constructor
        public Settings(Settings other) {
            // other will be null when creating the default settings
            if(other == null) return;
    
            // rest of copy constructor
        }
    }
    
    class Entry {
        public String name;
        public Settings settings = Config.defaultSettings; // use default if not set separately
    }
    
    class Config {
        public static Settings defaultSettings; // this is static
        public List<Entry> entries;
    
        // this is a setter that actually sets the static member
        public void setDefaultSettings(Settings defaultSettings) {
            Config.defaultSettings = defaultSettings);
    }
    

    Then with Jackson:

    ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
    Configuration config = mapper.readValue(configFile, Config.class);
    

    Essentially, this sets a static Settings (assuming that defaultSettings occurs first in the file). In the case where a partial settings block is specified in the yaml, the Settings 0-arg constructor will be used to , which creates a copy of Config.defaultSettings and then Jackson modifies it with whatever is specified in the file. In the case where no settings block is in the file for some Entry, Config.defaultSettings is used directly (via the field initialization in Entry). Not too bad, and beats writing a custom deserializer.

    I'm still interested if someone has a better way.