Search code examples
javayamlsnakeyaml

YAML Read casts exception


I have an error while reading a file

Exception in thread "main" java.lang.ClassCastException: java.lang.String can not be cast to java.util.ArrayList.

Why does it not occur? I'm using YamlSnake lib

Person.java:

public class Person
{

    int id;
    String fname;
    String lname;
    int age;

    public Person(int id, String fname, String lname, int age)
    {

        this.id = id;
        this.fname = fname;
        this.lname = lname;
        this.age = age;
    }
}

yaml.java:

@Override
public ArrayList<Person> Read() {

    ArrayList<Person> pp = new ArrayList<Person>();
    try 
    {
        FileReader fr = new FileReader("d:\\Person.yml");
        BufferedReader br = new BufferedReader(fr);
        String str=br.readLine();
        Yaml yml = new Yaml();
        pp=(ArrayList<Person>) yml.load(str);
        fr.close();

    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    return pp;
}

Person.yaml:

-id:2 fname:uu lname:uu age:45

Solution

  • You have errors in your YAML syntax. In a block mapping, only one key is allowed per line. Also, there must be a space after both - and : for them to be recognized as YAML special characters. Try:

    - id: 2
      fname: uu
      lname: uu
      age: 45
    

    If you want to keep it in one line, you can use a flow map instead:

    - {id: 2, fname: uu, lname: uu, age: 45 }
    

    Now the second problem is type erasure. You cannot tell SnakeYaml to load the YAML sequence into an ArrayList<Person> because at runtime, even if you tell SnakeYaml you want to have an ArrayList, it is impossible to determine the generic parameter Person. However, SnakeYaml does provide a possibility to overcome that restriction - alas, it requires you to change your YAML.

    Let this be your YAML:

    persons:
    - id: 2
      fname: uu
      lname: uu
      age:45
    

    Notice that I placed a YAML mapping as root element, which only contains a key persons whose value is the original YAML.

    Now, in Java, declare a class that matches this root element:

    class Root {
        List<Person> persons;
    }
    

    As before, type erasure prevents SnakeYaml to automatically determine the generic parameter of the type of persons at runtime. But now, we can use a TypeDescription to carry that information. Here's the code:

    @Override
    public List<Person> Read() {
        Constructor c = new Constructor(Root.class);
        TypeDescription td = new TypeDescription(Root.class);
        td.putListPropertyType("persons", Person.class);
        c.addTypeDescription(td);
        Yaml yaml = new Yaml(c);
    
        try  {
            FileReader fr = new FileReader("d:\\Person.yml");
            Root r = yaml.loadAs(fr, Root.class);
            fr.close();
            return r.persons;
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    
        return Collections.<Person>emptyList();
    }
    

    Beware of bugs in above code, I didn't test it.