Lesiak
Lesiak

Reputation: 25966

How to load a list of custom objects with SnakeYaml

I've been trying to deserialize the following yaml to a List<Stage> using SnakeYaml:

- name: Stage1
  items: 
    - item1
    - item2

- name: Stage2
  items: 
    - item3

public class Stage {
    private String name;
    private List<String> items;

    public Stage() {
    }

    public Stage(String name, List<String> items) {
        this.name = name;
        this.items = items;
    }

    // getters and setters
}

The closest question I found was SnakeYaml Deserialise Class containing a List of Objects. After reading it, I am aware of Constructor and TypeDescriptor classes, but I am still unable to get it working (I get list of HashMaps, not Stages).

The difference with the question in the link above is that my top-level structure is a list, not a custom object.

Upvotes: 11

Views: 4762

Answers (2)

raner
raner

Reputation: 1295

If you don't mind bringing in a dependency on Jackson and Jackson Dataformat YAML, this can be achieved in a single line of code, using Jackson's YAMLMapper in conjunction with a TypeReference:

import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class StageListTest
{
    @Test
    public void testLoadStage() throws Exception
    {
        // The YAML input file:
        InputStream yaml = getClass().getClassLoader().getResourceAsStream("stagelist.yml");

        // The expected result:
        List<Stage> expected = Arrays.asList
        (
            new Stage("Stage1", Arrays.asList("item1", "item2")),
            new Stage("Stage2", Arrays.asList("item3"))
        );

        // Use Jackson YAMLMapper to load file:
        List<Stage> result = new YAMLMapper().readValue(yaml, new TypeReference<List<Stage>>() {});

        // Make sure result is as expected:
        assertEquals(expected, result);
    }
}

(on top of getters and setters, hashCode and equals methods were added to the Stage type to support comparison of Stage objects)

The above solution uses SnakeYAML under the hood, but does not require creation of a custom type constructor.

Upvotes: 1

Andreas Berheim Brudin
Andreas Berheim Brudin

Reputation: 2280

One way would be to create your own snakeyaml Constructor like this:

public class ListConstructor<T> extends Constructor {
  private final Class<T> clazz;

  public ListConstructor(final Class<T> clazz) {
    this.clazz = clazz;
  }

  @Override
  protected Object constructObject(final Node node) {
    if (node instanceof SequenceNode && isRootNode(node)) {
      ((SequenceNode) node).setListType(clazz);
    }
    return super.constructObject(node);
  }

  private boolean isRootNode(final Node node) {
    return node.getStartMark().getIndex() == 0;
  }
}

and then use it when constructing the Yaml:

final Yaml yaml = new Yaml(new ListConstructor<>(Stage.class));

Upvotes: 8

Related Questions