Spring Configuration Properties - Polymorphic Objects in same List

956 Views Asked by At

I am trying to read a list of configuration items from application.yaml into a configuration bean via @ConfigurationProperties. The list may consist of objects with a defined base type:

@ConfigurationProperties("myapp")
public class MyConfiguration {
   private Set<Point> points;
}

public abstract class Point {
  private String name;
}

public class QuantityPoint extends Point {
  private Number value;
}

public class StatePoint extends Point {
  private String state;
}

Here an example application.yaml as I imagine it:

myapp:
  points:
  - name: foo
    value: 5
  - name: bar
    state: bar

I have found some posts which I thought are solving this exact question, but nothing suggested there has worked so far. I tried playing with ConfigurationProperties("myapp.points") on the subtypes on Point but as said: nothing has worked.

To be honest, I wonder how the parser should be able to distinguish the types. There must be something like a type argument missing similar to how we convert entities to json with Jackson.

At the moment, I am getting instances of Point (when I remove the abstract modifier) and the fields value and state are lost.

EDIT

I think I made some progress in finding out what needs to be done. Apparently, Spring is using SnakeYaml to parse the file and that library uses yaml tags to indicate type information so that my yaml should look something like

myapp:
  points:
  - !!foo
    name: foo
    value: 5
  - !!bar
    name: bar
    state: bar

There are some builtin tags but I would need to register my own. I found information how to write a custom Constructor by extending AbstractConstruct but I couldn't find anything on how to register this with the instance of Yaml that's set up by springboot.

Am I at least on the right track here?

EDIT

I noticed that the ConstructorException from snakeyaml comes so early in the startup process, it's probably not possible to to add my SafeConstructor to the process:

@Component
public class FooConstructor extends SafeConstructor {

    public FooConstructor() {
        this.yamlConstructors.put(new Tag("!foo"), new ConstructFoo());
    }
    
    private class ConstructFoo extends AbstractConstruct {
        @Override
        public Object construct(Node node) {
            String val = (String) constructScalar((ScalarNode) node);
            System.out.println(val);
            return null;
        }
    }
}
06:48:29.380 [restartedMain] ERROR org.springframework.boot.SpringApplication - Application run failed
org.yaml.snakeyaml.constructor.ConstructorException: could not determine a constructor for the tag !state
 in 'reader', line 5, column 5:
      - !foo
0

There are 0 best solutions below