Search code examples
javayamlsnakeyaml

Loading an abstract class based object by YAML file


I want to load object which contains array list of objects based on abstract class from yaml file. And i get this error message:

Exception in thread "LWJGL Application" Cannot create property=arrayListOfAbstractObjects for JavaBean=com.myyaml.test.ImplementationOfExampleClass@7a358cc1

in 'reader', line 1, column 1: dummyLong: 1 ^

java.lang.InstantiationException in 'reader', line 3, column 3: - dummyFloat: 444 ^

YAML file

dummyLong: 1
arrayListOfAbstractObjects:
  - dummyFloat: 444
  - dummyDouble: 123

Java classes:

public abstract class ExampleClass {
    protected ArrayList<AbstractClass> arrayListOfAbstractObjects;
    protected long dummyLong = 111;
    
    public ExampleClass() {
    }

    public void setArrayListOfAbstractObjects(ArrayList<AbstractClass> arrayListOfAbstractObjects) {
        this.arrayListOfAbstractObjects = arrayListOfAbstractObjects;
    }

    public void setDummyLong(long dummyLong) {
        this.dummyLong = dummyLong;
    }
}
public class ImplementationOfExampleClass extends ExampleClass {
    
    public ImplementationOfExampleClass() {
    }
}
public abstract class AbstractClass {
    private int dummyInt = 22;
    
    public AbstractClass() {
    }

    public void setDummyInt(int dummyInt) {
        this.dummyInt = dummyInt;
    }
}
public class FirstImplementationOfAbstractClass extends AbstractClass {
    float dummyFloat = 111f;
    
    public FirstImplementationOfAbstractClass() {
    }

    public void setDummyFloat(float dummyFloat) {
        this.dummyFloat = dummyFloat;
    }
}
public class SecondImplementationOfAbstractClass extends AbstractClass {
    double dummyDouble = 333f;
    
    public SecondImplementationOfAbstractClass() {
    }

    public void setDummyDouble(double dummyDouble) {
        this.dummyDouble = dummyDouble;
    }
}

My guess is that yaml doesn't know which sort of abstract class implementation to use. FirstImplementationOfAbstractClass or SecondImplementationOfAbstractClass. Is it possible to load an object by yaml with such classes?


Solution

  • This is only possible if you tell the YAML processor which class you want to instantiate on the YAML side. You do this with tags:

    dummyLong: 1
    arrayListOfAbstractObjects:
      - !first
        dummyFloat: 444
      - !second
        dummyDouble: 123
    

    Then, you can instruct your YAML processor to properly process the items based on their tags. E.g. with SnakeYAML, you would do

    class MyConstructor extends Constructor {
        public MyConstructor() {
            this.yamlConstructors.put(new Tag("!first"), new ConstructFirst());
            this.yamlConstructors.put(new Tag("!second"), new ConstructSecond());
        }
    
        private class ConstructFirst extends AbstractConstruct {
            public Object construct(Node node) {
                // raw values, as if you would have loaded the content into a generic map.
                final Map<Object, Object> values = constructMapping(node);
                final FirstImplementationOfAbstractClass ret =
                        new FirstImplementationOfAbstractClass();
                ret.setDummyFloat(Float.parseFloat(values.get("dummyFloat").toString()));
                return ret;
            }
        }
    
        private class ConstructSecond extends AbstractConstruct {
            public Object construct(Node node) {
                final Map<Object, Object> values = constructMapping(node);
                final SecondImplementationOfAbstractClass ret =
                        new SecondImplementationOfAbstractClass();
                ret.setDummyFloat(Double.parseDouble(values.get("dummyFloat").toString()));
                return ret;
            }
        }
    }
    
    

    Note: You can be more intelligent when loading the content, avoiding toString and instead process the node content directly; I use a dumb implementation for easy demonstration.

    Then, you use this constructor:

    Yaml yaml = new Yaml(new MyConstructor());
    ExampleClass loaded = yaml.loadAs(input, ImplementationOfExampleClass.class);