rocksNwaves
rocksNwaves

Reputation: 6154

How to instantiate new objects with object parameters using YAML config file?

I am trying to use a config file to allow a user to choose a concrete clustering algorithm that implements some abstract class or interface. Each algorithm may have slightly different pre-requisite inputs. A small conceptual example:

The things the two have in common are defined in an abstract class.

To make things more general, I made up a dummy example. Let's say I have a Configuration class that will be passed to an "engine" or "driver" of sorts to complete a job based on the instance of Configuration generated.

public class Configuration {

    private AbstractAlgorithm algo;
    private AbstractParameter param1;
    private ConcreteEnum.Enum1 param2;
    private ConcreteEnum.Enum2 param3;
   
    // getters and setters...

Then I have the classes

abstract class AbstractAlgorithm{
    AbstractParameter abstractParam;
    public void setParam(AbstractParam p) {
        this.abstractParam = p;
    }

    public RandomObject runAlgo(DataObject data);
}

and ConcreteAlgorithm:

public class ConcreteAlgorithm extends AbstractAlgorithm {

    ConcreteEnum.enum1 concreteParam1;
    ConcreteEnum.enum2 concreteParam2;

    public ConcreteAlgorithm(ConcreteParameter p1, ConcreteParameter p2) {
        this.concreteParam1 = p1;
        this.concreteParam2 = p2;
    }

    public abstract void runAlgo(DataObject data) {
        if (abstractParam == null) {
            throw new IllegalStateException();
        } else {
        // do calculations based on enums and abstractParam value
        }
    }

My question then is: how would the accompanying .yaml file look so that it could be parsed into an instantiated Configuration object that could be passed on to something that uses it?

Upvotes: 0

Views: 1635

Answers (1)

flyx
flyx

Reputation: 39708

As usual with serialization, your classes should have a constructor with no arguments:

abstract class AbstractAlgorithm {
    public AbstractParameter abstractParam;
}

public class ConcreteAlgorithm extends AbstractAlgorithm {
    public ConcreteEnum.Enum1 param1;
    public ConcreteEnum.Enum2 param2;
}

Whatever interface they have besides the fields is irrelevant to this question. I made the fields public since there is not really a point for them not to be when you're doing deserialization unless you want to process the given values in setters, in which case you would use setters.

Now with these types defined, loading will work as follows:

Constructor constructor = new Constructor();
constructor.addTypeDescription(
    new TypeDescription(ConcreteAlgorithm.class, new Tag("!concrete")));
yaml = new Yaml(constructor);
AbstractAlgorithm = yaml.loadAs(someInput, AbstractAlgorithm.class);

and the YAML being loaded would look like this:

--- !concrete
abstractParam: spam
param1: egg
param2: sausage

The --- is the directives end marker, which starts the document's content and is optional. We use it here to make clear that the following tag applies to the root node of the document.

!concrete is that tag for the root node. Since we register it with SnakeYAML, it will lead to SnakeYAML constructing a ConcreteAlgorithm object from this file.

The following key-value pairs give values to each of the fields of a ConcreteAlgorithm, including the inherited field. SnakeYAML will process the field values depending on the nature of the used classes – AbstractParameter may want to have a nested mapping in the YAML file if it's a class. If AbstractParameter is another abstract class and has ConcreteParameter as implementation, you would do

--- !concrete
abstractParam: !cp
  droggel: jug
param1: egg
param2: sausage

and register !cp as tag for ConcreteParameter. The nested mapping containing the key droggel will then generate an instance of ConcreteParameter and be assigned to the field abstractParam.

Since you can give tags on any level, you can nest fields with abstract class type anywhere in the structure you load your YAML into.

Upvotes: 2

Related Questions