title: Build custom SnakeYAML Constructor to deserialize yaml file in a modular way
body: <p>I would like to parse yaml files like the following using SnakeYAML:</p>
  <pre class="lang-yaml prettyprint-override"><code>config:
    someBoolean: true
    someString: testing action descriptors
  actions:
  - print: Hello world
  - print: Next action is add
  - add:
      left: 25
      right: 17
  - print: done
  </code></pre>
  <p>The target type for this document is <code>DocumentRoot</code>:</p>
  <pre class="lang-java prettyprint-override"><code>public class DocumentRoot {
      public Config config;
      public List&lt;Map&lt;String, Object&gt;&gt; actions;
  }
  
  public class Config {
      public String someString;
      public boolean someBoolean;
  }
  </code></pre>
  <p>So most of the document should be parsed by SnakeYAML directly into Java-Objects like the <code>config</code>-Attribute. However the <code>actions</code>-Attribute should be parsed in a modular way. Consider the following <code>ActionDescriptor</code>s:</p>
  <pre class="lang-java prettyprint-override"><code>public interface ActionDescriptor&lt;T&gt; {
      String actionKey();
  
      Class&lt;T&gt; actionValueType();
  
      void runAction(T actionValue);
  }
  
  public class AddExpression {
      public int left;
      public int right;
  }
  
  private static List&lt;ActionDescriptor&lt;?&gt;&gt; createDescriptors() {
      return List.of(new ActionDescriptor&lt;String&gt;() {
          @Override
          public String actionKey() {
              return &quot;print&quot;;
          }
  
          @Override
          public Class&lt;String&gt; actionValueType() {
              return String.class;
          }
  
          @Override
          public void runAction(String actionValue) {
              System.out.println(actionValue);
          }
      }, new ActionDescriptor&lt;AddExpression&gt;() {
          @Override
          public String actionKey() {
              return &quot;add&quot;;
          }
  
          @Override
          public Class&lt;AddExpression&gt; actionValueType() {
              return AddExpression.class;
          }
  
          @Override
          public void runAction(AddExpression actionValue) {
              System.out.println(&quot;calculated: &quot; + (actionValue.left + actionValue.right));
          }
      });
  }
  </code></pre>
  <p>I would now like to use these <code>ActionDescriptor</code>s to use the <code>actions</code> attribute in the following way:</p>
  <pre class="lang-java prettyprint-override"><code>public static void main(String[] args) throws IOException {
      List&lt;ActionDescriptor&lt;?&gt;&gt; descriptors = createDescriptors();
      DocumentRoot documentRoot = createYaml(descriptors).loadAs(new FileInputStream(&quot;data/input.yaml&quot;),
              DocumentRoot.class);
      Map&lt;String, ActionDescriptor&lt;?&gt;&gt; descriptorMap = descriptors.stream()
              .collect(Collectors.toMap(ActionDescriptor::actionKey, Function.identity()));
      if (documentRoot.config.someBoolean) {
          System.out.println(documentRoot.config.someString);
          for (Map&lt;String, Object&gt; actionMap : documentRoot.actions) {
              for (Entry&lt;String, Object&gt; entry : actionMap.entrySet()) {
                  runAction(entry.getValue(), descriptorMap.get(entry.getKey()));
              }
          }
      }
  }
  
  private static &lt;T&gt; void runAction(Object actionValue, ActionDescriptor&lt;T&gt; descriptor) {
      Class&lt;T&gt; valueType = descriptor.actionValueType();
      if (valueType.isInstance(actionValue)) {
          descriptor.runAction(valueType.cast(actionValue));
      } else {
          System.out.println(&quot;expected '&quot; + valueType + &quot;' but got '&quot; + actionValue.getClass() + &quot;'&quot;);
      }
  }
  </code></pre>
  <p>Currently I use the following method to create the <code>Yaml</code> instance of SnakeYAML:</p>
  <pre class="lang-java prettyprint-override"><code>private static Yaml createYaml(List&lt;ActionDescriptor&lt;?&gt;&gt; descriptors) {
      Constructor constructor = new Constructor(DocumentRoot.class);
      for (ActionDescriptor&lt;?&gt; descriptor : descriptors) {
          // ???
          constructor.addTypeDescription(new TypeDescription(descriptor.actionValueType()));
      }
      Yaml yaml = new Yaml(constructor);
      yaml.setBeanAccess(BeanAccess.FIELD);
      return yaml;
  }
  </code></pre>
  <p>When running the program I get the following output:</p>
  <blockquote>
  <pre><code>testing action descriptors
  Hello world
  Next action is add
  expected 'class animatex.so.AddExpression' but got 'class java.util.LinkedHashMap'
  done
  </code></pre>
  </blockquote>
  <p>But I would like to have the following one:</p>
  <blockquote>
  <pre><code>testing action descriptors
  Hello world
  Next action is add
  calculated: 42
  done
  </code></pre>
  </blockquote>
  <p>Clearly SnakeYAML does not use the desired types for the deserialization of the action values. So I need to somehow tell SnakeYaml at the location <code>???</code> that if it deserializes a value in a map entry (whose map is an entry in the list in the attribute <code>actions</code>) then it should use the type <code>descriptor.actionValueType()</code> if the respective key of the map entry is <code>descriptor.actionKey()</code>.</p>
  <p>I already tried several things using <code>TypeDescriptor</code>s, <code>Constructor</code>s and <code>Construct</code>s and digged into the Code of SnakeYaml, but I simply do not really understand how it works so I am unable to build a working constructor for this use case.</p>
  <p>If it helps, I can also extend the <code>ActionDescriptor</code> interface to provide a <code>TypeDescriptor</code>, <code>Constructor</code>, <code>Construct</code> ...</p>
  <p>I would really like to avoid adding tags to the yaml file, but if there is no other solution I might bite that bullet.</p>
  <p>My question is: How can I build such a <code>Constructor</code>? Looking forward to your comments and answers :-)</p>