Cyotek Development Bloghttps://devblog.cyotek.com/tag/type/atom.xml2017-04-24T19:31:29ZUsing custom type converters with C# and YamlDotNet, part 2urn:uuid:5cb26e3d-52a9-41db-b50d-0392dd965bcd2017-04-24T19:31:29Z2017-04-24T19:31:29Z<p>Recently I discussed using type converters to perform custom
serialization of types in YamlDotNet. In this post I'll
concentrate on expanding the type converter to support
deserialization as well.</p>
<p>I'll be reusing a lot of code and knowledge from the <a href="/post/using-custom-type-converters-with-csharp-and-yamldotnet-part-1">first
part</a> of this mini-series, so if you haven't read that yet it
is a good place to start.</p>
<blockquote>
<p>Even more so that with part 1, in this article I'm completely
winging it. This code works in my demonstration program but
I'm by no means confident it is error free or the best way of
reading YAML objects.</p>
</blockquote>
<p>To deserialize data via a type converter, we need to implement
the <code>ReadYaml</code> method of the <code>IYamlTypeConverter</code> interface.
This method provides an object implementing <code>IParser</code> for
reading the YAML, along with a <code>type</code> parameter describing the
type of object the method should return. This latter parameter
can be ignored unless your converter can handle multiple object
types.</p>
<p>The <code>IParser</code> interface itself is very basic - a <code>MoveNext</code>
method to advance the parser, and a <code>Current</code> property which
returns the current <code>ParsingEvent</code> object (the same types of
object we originally used to write the YAML).</p>
<p>YamlDotNet also adds a few extension methods to this interface
which may be of use. Although in this sample project I'm only
using the base interface, I try to point out where you could use
these extension methods which you may find more readable to use.</p>
<p>A key tip is to always advance the parser by calling <code>MoveNext</code>
- if you don't, then YamlDotNet will call your converter again
and again in an infinite loop. This is the very first issue I
encountered when I wrote some placeholder code as below and then
ran the demo program.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">object</span> ReadYaml<span class="symbol">(</span>IParser parser<span class="symbol">,</span> Type type<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="comment">// As we&#39;re not advancing the parser, we&#39;ve just introduced an infinte loop</span>
 <span class="keyword">return</span> <span class="keyword">new</span> ContentCategory<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>You should probably consider having automated tests that run as
you're writing the code using a tool such as NCrunch. Just as
with serializing, I found writing deserialization code using
YamlDotNet to be non-intuitive and debugging counter productive.</p>
<h2 id="reading-property-maps">Reading property maps</h2>
<p>To read a map, we first check to ensure the current element is
<code>MappingStart</code> instance. Then just keep reading and processing
nodes until we get a corresponding <code>MappingEnd</code> object.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">readonly</span> Type _mappingStartType <span class="symbol">=</span> <span class="keyword">typeof</span><span class="symbol">(</span>MappingStart<span class="symbol">)</span><span class="symbol">;</span>
<span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">readonly</span> Type _mappingEndType <span class="symbol">=</span> <span class="keyword">typeof</span><span class="symbol">(</span>MappingEnd<span class="symbol">)</span><span class="symbol">;</span>

<span class="keyword">public</span> <span class="keyword">object</span> ReadYaml<span class="symbol">(</span>IParser parser<span class="symbol">,</span> Type type<span class="symbol">)</span>
<span class="symbol">{</span>
 ContentCategory result<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>parser<span class="symbol">.</span>Current<span class="symbol">.</span>GetType<span class="symbol">(</span><span class="symbol">)</span> <span class="symbol">!=</span> _mappingStartType<span class="symbol">)</span> <span class="comment">// You could also use parser.Accept&lt;MappingStart&gt;()</span>
 <span class="symbol">{</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> InvalidDataException<span class="symbol">(</span><span class="string">&quot;Invalid YAML content.&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 parser<span class="symbol">.</span>MoveNext<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span> <span class="comment">// move on from the map start</span>

 result <span class="symbol">=</span> <span class="keyword">new</span> ContentCategory<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">do</span>
 <span class="symbol">{</span>
 <span class="comment">// do something with the current node</span>

 parser<span class="symbol">.</span>MoveNext<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span> <span class="keyword">while</span> <span class="symbol">(</span>parser<span class="symbol">.</span>Current<span class="symbol">.</span>GetType<span class="symbol">(</span><span class="symbol">)</span> <span class="symbol">!=</span> _mappingEndType<span class="symbol">)</span><span class="symbol">;</span>

 parser<span class="symbol">.</span>MoveNext<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span> <span class="comment">// skip the mapping end (or crash)</span>

 <span class="keyword">return</span> result<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>With the basics in place, we can now process the nodes inside
our loop. As it is a mapping, any value should be preceded by a
scalar name and often will be followed by a simple scalar value.
For this reason I added a helper method to check if the current
node is a <code>Scalar</code> and if so return its value (otherwise to
throw an exception).</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">string</span> GetScalarValue<span class="symbol">(</span>IParser parser<span class="symbol">)</span>
<span class="symbol">{</span>
 Scalar scalar<span class="symbol">;</span>

 scalar <span class="symbol">=</span> parser<span class="symbol">.</span>Current <span class="keyword">as</span> Scalar<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>scalar <span class="symbol">==</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> InvalidDataException<span class="symbol">(</span><span class="string">&quot;Failed to retrieve scalar value.&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 
 <span class="comment">// You could replace the above null check with parser.Expect&lt;Scalar&gt; which will throw its own exception</span>
 
 <span class="keyword">return</span> scalar<span class="symbol">.</span>Value<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Inside the main processing loop, I get the scalar value that
represents the name of the property to process and advance the
reader to get it ready to process the property value. I then
check the property name and act accordingly depending on if it
is a simple or complex type.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">string</span> value<span class="symbol">;</span>

value <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetScalarValue<span class="symbol">(</span>parser<span class="symbol">)</span><span class="symbol">;</span>
parser<span class="symbol">.</span>MoveNext<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span> <span class="comment">// skip the scalar property name</span>

<span class="keyword">switch</span> <span class="symbol">(</span>value<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">case</span> <span class="string">&quot;Name&quot;</span><span class="symbol">:</span>
 result<span class="symbol">.</span>Name <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetScalarValue<span class="symbol">(</span>parser<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">case</span> <span class="string">&quot;Title&quot;</span><span class="symbol">:</span>
 result<span class="symbol">.</span>Title <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetScalarValue<span class="symbol">(</span>parser<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">case</span> <span class="string">&quot;Topics&quot;</span><span class="symbol">:</span>
 <span class="keyword">this</span><span class="symbol">.</span>ReadTopics<span class="symbol">(</span>parser<span class="symbol">,</span> result<span class="symbol">.</span>Topics<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">case</span> <span class="string">&quot;Categories&quot;</span><span class="symbol">:</span>
 <span class="keyword">this</span><span class="symbol">.</span>ReadContentCategories<span class="symbol">(</span>parser<span class="symbol">,</span> result<span class="symbol">.</span>Categories<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">default</span><span class="symbol">:</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> InvalidDataException<span class="symbol">(</span><span class="string">&quot;Unexpected scalar value &#39;&quot;</span> <span class="symbol">+</span> value <span class="symbol">+</span> <span class="string">&quot;&#39;.&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>For the sample <code>Name</code> and <code>Title</code> properties of my
<code>ContentCategory</code> object, I use the <code>GetScalarValue</code> helper
method above to just return the string value. The <code>Topics</code> and
<code>Categories</code> properties however are collection objects, which
leads us nicely to the next section.</p>
<h2 id="reading-lists">Reading lists</h2>
<p>Reading lists is fairly similar to maps, except this time we
start by looking for <code>SequenceStart</code> and ending with
<code>SequenceEnd</code>. Otherwise the logic is fairly similar. For
example, in the demonstration project, the <code>Topics</code> property is
a list of strings and therefore can be easily read by reading
each scalar entry in the sequence.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">readonly</span> Type _sequenceEndType <span class="symbol">=</span> <span class="keyword">typeof</span><span class="symbol">(</span>SequenceEnd<span class="symbol">)</span><span class="symbol">;</span>
<span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">readonly</span> Type _sequenceStartType <span class="symbol">=</span> <span class="keyword">typeof</span><span class="symbol">(</span>SequenceStart<span class="symbol">)</span><span class="symbol">;</span>

<span class="keyword">private</span> <span class="keyword">void</span> ReadTopics<span class="symbol">(</span>IParser parser<span class="symbol">,</span> StringCollection topics<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>parser<span class="symbol">.</span>Current<span class="symbol">.</span>GetType<span class="symbol">(</span><span class="symbol">)</span> <span class="symbol">!=</span> _sequenceStartType<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> InvalidDataException<span class="symbol">(</span><span class="string">&quot;Invalid YAML content.&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 parser<span class="symbol">.</span>MoveNext<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span> <span class="comment">// skip the sequence start</span>

 <span class="keyword">do</span>
 <span class="symbol">{</span>
 topics<span class="symbol">.</span>Add<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>GetScalarValue<span class="symbol">(</span>parser<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 parser<span class="symbol">.</span>MoveNext<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span> <span class="keyword">while</span> <span class="symbol">(</span>parser<span class="symbol">.</span>Current<span class="symbol">.</span>GetType<span class="symbol">(</span><span class="symbol">)</span> <span class="symbol">!=</span> _sequenceEndType<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Sequences don't have to be lists of simple values, they can be
complex objects of their own. As our <code>ContentCategory</code> object
can have children of the same type, another helper method
repeatedly calls the base <code>ReadYaml</code> method to construct child
objects.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> ReadContentCategories<span class="symbol">(</span>IParser parser<span class="symbol">,</span> ContentCategoryCollection categories<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>parser<span class="symbol">.</span>Current<span class="symbol">.</span>GetType<span class="symbol">(</span><span class="symbol">)</span> <span class="symbol">!=</span> _sequenceStartType<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> InvalidDataException<span class="symbol">(</span><span class="string">&quot;Invalid YAML content.&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 parser<span class="symbol">.</span>MoveNext<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span> <span class="comment">// skip the sequence start</span>

 <span class="keyword">do</span>
 <span class="symbol">{</span>
 categories<span class="symbol">.</span>Add<span class="symbol">(</span><span class="symbol">(</span>ContentCategory<span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>ReadYaml<span class="symbol">(</span>parser<span class="symbol">,</span> <span class="keyword">null</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span> <span class="keyword">while</span> <span class="symbol">(</span>parser<span class="symbol">.</span>Current<span class="symbol">.</span>GetType<span class="symbol">(</span><span class="symbol">)</span> <span class="symbol">!=</span> _sequenceEndType<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>What I don't know how to do however, is invoke the original
parser logic for handling other types. Nor do I know how our
custom type converters are supposed to make use of
<code>INamingConvention</code> implementations. The demo project is using
capitalisation, but the production code is using pure lowercase
to avoid any ambiguity.</p>
<h2 id="using-the-custom-type-converter">Using the custom type converter</h2>
<p>Just as we did with the <code>SerializerBuilder</code> in part 1, we use
the <code>WithTypeConverter</code> method on a <code>DeserializerBuilder</code>
instance to inform YamlDotNet of the existence of our converter.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
Deserializer deserializer<span class="symbol">;</span>

deserializer <span class="symbol">=</span> <span class="keyword">new</span> DeserializerBuilder<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">.</span>WithTypeConverter<span class="symbol">(</span><span class="keyword">new</span> ContentCategoryYamlTypeConverter<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">.</span>Build<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<p>It would be nice if I could decorate my types with a YamlDotNet
version of the standard <code>TypeConverter</code> attribute and so avoid
having to manually use <code>WithTypeConverter</code> but this doesn't seem
to be a supported feature.</p>
<h2 id="closing">Closing</h2>
<p>Custom YAML serialization and deserialization with YamlDotNet
isn't as straightforward as perhaps could be but it isn't
difficult to do. Even better, if you serialize valid YAML then
it's entirely possible (as in my case where I'm attempting to
serialize less default values) that you don't need to write
custom deserialization code at all as YamlDotNet will handle it
for you.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2017-04-24 - First published</li>
<li>2020-11-22 - Updated formatting</li>
</ul>

<p><small>
All content <a href="https://devblog.cyotek.com/copyright-and-trademarks">Copyright (c) by Cyotek Ltd</a> or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.<br />Original URL of this content is https://devblog.cyotek.com/post/using-custom-type-converters-with-csharp-and-yamldotnet-part-2 .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comUsing custom type converters with C# and YamlDotNet, part 1urn:uuid:1b8d8ca1-3ac4-4ae4-91ae-a30c9e17a0312017-04-24T18:20:21Z2017-04-01T16:36:51Z<p>One of our internal tools eschews XML or JSON configuration
files in favour of something more human readable - <a href="http://yaml.org/" rel="external nofollow noopener">YAML</a>
using <a href="http://aaubry.net/pages/yamldotnet.html" rel="external nofollow noopener">YamlDotNet</a>. For the most part the serialisation and
deserialisation of YAML documents in .NET objects is as straight
forward as using libraries such as <a href="http://www.newtonsoft.com/json" rel="external nofollow noopener">JSON.net</a> but when I was
working on some basic serialisation there were a few issues.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/yaml-serialisation-1a.png" class="gallery" title="A demonstration program showing the basics of YAML serialisation" ><img src="https://images.cyotek.com/image/thumbnail/devblog/yaml-serialisation-1a.png" alt="A demonstration program showing the basics of YAML serialisation" decoding="async" loading="lazy" /></a><figcaption>A demonstration program showing the basics of YAML serialisation</figcaption></figure><h2 id="setting-the-scene">Setting the scene</h2>
<p>For this demonstration project, I'm going to use a pair of basic
classes.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">internal</span> <span class="keyword">sealed</span> <span class="keyword">class</span> ContentCategoryCollection <span class="symbol">:</span> Collection<span class="symbol">&lt;</span>ContentCategory<span class="symbol">&gt;</span>
<span class="symbol">{</span>
 <span class="keyword">private</span> ContentCategory _parent<span class="symbol">;</span>

 <span class="keyword">public</span> ContentCategory Parent
 <span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _parent<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">set</span>
 <span class="symbol">{</span>
 _parent <span class="symbol">=</span> value<span class="symbol">;</span>

 <span class="keyword">foreach</span> <span class="symbol">(</span>ContentCategory item <span class="keyword">in</span> <span class="keyword">this</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 item<span class="symbol">.</span>Parent <span class="symbol">=</span> value<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> InsertItem<span class="symbol">(</span><span class="keyword">int</span> index<span class="symbol">,</span> ContentCategory item<span class="symbol">)</span>
 <span class="symbol">{</span>
 item<span class="symbol">.</span>Parent <span class="symbol">=</span> _parent<span class="symbol">;</span>

 <span class="keyword">base</span><span class="symbol">.</span>InsertItem<span class="symbol">(</span>index<span class="symbol">,</span> item<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>

<span class="keyword">internal</span> <span class="keyword">sealed</span> <span class="keyword">class</span> ContentCategory
<span class="symbol">{</span>
 <span class="keyword">private</span> ContentCategoryCollection _categories<span class="symbol">;</span>

 <span class="keyword">private</span> StringCollection _topics<span class="symbol">;</span>

 <span class="symbol">[</span>Browsable<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">]</span>
 <span class="keyword">public</span> ContentCategoryCollection Categories
 <span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _categories <span class="symbol">??</span> <span class="symbol">(</span>_categories <span class="symbol">=</span> <span class="keyword">new</span> ContentCategoryCollection <span class="symbol">{</span> Parent <span class="symbol">=</span> <span class="keyword">this</span> <span class="symbol">}</span><span class="symbol">)</span><span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">set</span> <span class="symbol">{</span> _categories <span class="symbol">=</span> value<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="symbol">[</span>Browsable<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">]</span>
 <span class="symbol">[</span>DesignerSerializationVisibility<span class="symbol">(</span>DesignerSerializationVisibility<span class="symbol">.</span>Hidden<span class="symbol">)</span><span class="symbol">]</span>
 <span class="symbol">[</span>DefaultValue<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">]</span>
 <span class="keyword">public</span> <span class="keyword">bool</span> HasCategories
 <span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _categories <span class="symbol">!=</span> <span class="keyword">null</span> <span class="symbol">&amp;&amp;</span> _categories<span class="symbol">.</span>Count <span class="symbol">!=</span> <span class="number">0</span><span class="symbol">;</span> <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="symbol">[</span>Browsable<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">]</span>
 <span class="symbol">[</span>DesignerSerializationVisibility<span class="symbol">(</span>DesignerSerializationVisibility<span class="symbol">.</span>Hidden<span class="symbol">)</span><span class="symbol">]</span>
 <span class="symbol">[</span>DefaultValue<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">]</span>
 <span class="keyword">public</span> <span class="keyword">bool</span> HasTopics
 <span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _topics <span class="symbol">!=</span> <span class="keyword">null</span> <span class="symbol">&amp;&amp;</span> _topics<span class="symbol">.</span>Count <span class="symbol">!=</span> <span class="number">0</span><span class="symbol">;</span> <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">public</span> <span class="keyword">string</span> Name <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>

 <span class="symbol">[</span>Browsable<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">]</span>
 <span class="keyword">public</span> ContentCategory Parent <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>

 <span class="keyword">public</span> <span class="keyword">string</span> Title <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>

 <span class="symbol">[</span>Browsable<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">]</span>
 <span class="keyword">public</span> StringCollection Topics
 <span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _topics <span class="symbol">??</span> <span class="symbol">(</span>_topics <span class="symbol">=</span> <span class="keyword">new</span> StringCollection<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">set</span> <span class="symbol">{</span> _topics <span class="symbol">=</span> value<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The classes are fairly simple, but they do offer some small
challenges for serialisation</p>
<ul>
<li>Read-only properties</li>
<li>Parent references</li>
<li>Special values - child collections that are only initialised
when they are accessed and should be ignored if null or empty</li>
</ul>
<h2 id="basic-serialisation">Basic serialisation</h2>
<p>Using YamlDotNet, you can serialise an object graph quite simply
enough</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
Serializer serializer<span class="symbol">;</span>
<span class="keyword">string</span> yaml<span class="symbol">;</span>

serializer <span class="symbol">=</span> <span class="keyword">new</span> SerializerBuilder<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">.</span>Build<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

yaml <span class="symbol">=</span> serializer<span class="symbol">.</span>Serialize<span class="symbol">(</span>_categories<span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<h2 id="basic-deserialisation">Basic deserialisation</h2>
<p>Deserialising a YAML document into a .NET object is also quite
straightforward</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
Deserializer deserializer<span class="symbol">;</span>

deserializer <span class="symbol">=</span> <span class="keyword">new</span> DeserializerBuilder<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">.</span>Build<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

<span class="keyword">using</span> <span class="symbol">(</span>Stream stream <span class="symbol">=</span> File<span class="symbol">.</span>OpenRead<span class="symbol">(</span>fileName<span class="symbol">)</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>TextReader reader <span class="symbol">=</span> <span class="keyword">new</span> StreamReader<span class="symbol">(</span>stream<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 _categories <span class="symbol">=</span> deserializer<span class="symbol">.</span>Deserialize<span class="symbol">&lt;</span>ContentCategoryCollection<span class="symbol">&gt;</span><span class="symbol">(</span>reader<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="serialisation-shortcomings">Serialisation shortcomings</h2>
<p>The following is an example of the YAML produced by the above
classes with default serialisation</p>
<figure class="lang-yaml highlight"><figcaption><span>yaml</span></figcaption><pre class="code">
- Categories: []
 HasTopics: true
 Name: intro
 Title: Introducing {{ applicationname }}
 Topics:
 - whatis.md
 - licenseagreement.md
- &amp;o0
 Categories:
 - Categories: []
 Name: userinterface
 Parent: *o0
 Title: User Interface
 Topics: []
 HasCategories: true
 Name: gettingstarted
 Title: Getting Started
 Topics: []
- Categories: []
 Name: blank
 Title: Blank
 Topics: []
</pre>
</figure>
<p>For a format that is &quot;human friendly&quot; this is quite verbose with
a lot of extra clutter as the serialisation has included the
read-only properties (which will then cause a crash on
deserialisation), and our create-on-demand collections are being
created and serialised as empty values. It is also slightly
alien when you consider the alias references. While those are
undeniably cool (especially as YamlDotNet will recreate the
references), the nested nature of the properties implicitly
indicate the relationships and are therefore superfluous in this
case</p>
<p>It's also worth pointing out that the order of the serialised
values matches the ordering in code file - I always format my
code files to order members alphabetically, so the properties
are also serialised alphabetically.</p>
<p>You can also see that, for the most part, the <code>HasCategories</code>
and <code>HasTopics</code> properties were not serialised - although
YamlDotNet is ignoring the <code>BrowsableAttribute</code>, it is
processing the <code>DefaultValueAttribute</code> and skipping values which
are considered default, which is another nice feature.</p>
<h2 id="resolving-some-issues">Resolving some issues</h2>
<p>Similar to Json.NET, you can decorate your classes with
attributes to help control serialisation, and so we'll
investigate these first to see if they can resolve our problems
simply and easily.</p>
<h3 id="excluding-read-only-properties">Excluding read-only properties</h3>
<p>The <code>YamlIgnoreAttribute</code> class can be used to force certain
properties to be skipped, so applying this attribute to
properties with only getters is a good idea.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>YamlIgnore<span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">bool</span> HasCategories
<span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _categories <span class="symbol">!=</span> <span class="keyword">null</span> <span class="symbol">&amp;&amp;</span> _categories<span class="symbol">.</span>Count <span class="symbol">!=</span> <span class="number">0</span><span class="symbol">;</span> <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h3 id="changing-serialisation-order">Changing serialisation order</h3>
<p>We can control the order in which YamlDotNet serialises using
the <code>YamlMemberAttribute</code>. This attribute has various options,
but for the time being I'm just looking at ordering - I'll
revisit this attribute in the next post.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>YamlMember<span class="symbol">(</span>Order <span class="symbol">=</span> <span class="number">1</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">string</span> Name <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>
</pre>
</figure>
<blockquote>
<p>If you specify this attribute on one property to set an order
you'll most likely need to set it on all.</p>
</blockquote>
<h3 id="processing-the-collection-properties">Processing the collection properties</h3>
<p>Unfortunately, while I could make use of the <code>YamlIgnore</code> and
<code>YamlMember</code> attributes to control some of the serialisation, it
wouldn't stop the empty collection nodes from being created and
then serialised, which I didn't want. I suppose I could finally
work out how to make <code>DefaultValue</code> apply to collection classes
effectively, but then there wouldn't be much point in this
article!</p>
<p>Due to this requirement, I'm going to need to write some custom
serialisation code - enter the <code>IYamlTypeConveter</code> interface.</p>
<h2 id="creating-a-custom-converter">Creating a custom converter</h2>
<p>To create a custom converter for use with YamlDotNet, we start
by creating a new class and implementing <code>IYamlTypeConverter</code>.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">internal</span> <span class="keyword">sealed</span> <span class="keyword">class</span> ContentCategoryYamlTypeConverter <span class="symbol">:</span> IYamlTypeConverter
<span class="symbol">{</span>
 <span class="keyword">public</span> <span class="keyword">bool</span> Accepts<span class="symbol">(</span>Type type<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="symbol">}</span>

 <span class="keyword">public</span> <span class="keyword">object</span> ReadYaml<span class="symbol">(</span>IParser parser<span class="symbol">,</span> Type type<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="symbol">}</span>

 <span class="keyword">public</span> <span class="keyword">void</span> WriteYaml<span class="symbol">(</span>IEmitter emitter<span class="symbol">,</span> <span class="keyword">object</span> value<span class="symbol">,</span> Type type<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>First thing is to specify what types our class can handle via
the <code>Accepts</code> method.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">readonly</span> Type _contentCategoryNodeType <span class="symbol">=</span> <span class="keyword">typeof</span><span class="symbol">(</span>ContentCategory<span class="symbol">)</span><span class="symbol">;</span>

<span class="keyword">public</span> <span class="keyword">bool</span> Accepts<span class="symbol">(</span>Type type<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">return</span> type <span class="symbol">==</span> _contentCategoryNodeType<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>In this case, we only care about our <code>ContentCategory</code> class so
I return <code>true</code> for this type and <code>false</code> for anything else.</p>
<p>Next, it's time to write the YAML content via the <code>WriteYaml</code>
method.</p>
<blockquote>
<p>The documentation for YamlDotNet is a little lacking and I
didn't find the serialisation support to be particularly
intuitive, so the code I'm presenting below is what worked for
me, but there may be better ways of doing it.</p>
</blockquote>
<p>First we need to get the value to serialise - this is via the
<code>value</code> and <code>type</code> parameters. In my example, I can ignore
<code>type</code> though as I'm only supporting the one type.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">void</span> WriteYaml<span class="symbol">(</span>IEmitter emitter<span class="symbol">,</span> <span class="keyword">object</span> value<span class="symbol">,</span> Type type<span class="symbol">)</span>
<span class="symbol">{</span>
 ContentCategory node<span class="symbol">;</span>

 node <span class="symbol">=</span> <span class="symbol">(</span>ContentCategory<span class="symbol">)</span>value<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The <code>IEmitter</code> interface (accessed via the <code>emitter</code> parameter)
is similar in principle to JSON.net's <code>JsonTextWriter</code> class
except it is less developer friendly. Rather than having a
number of <code>Write*</code> methods or overloads similar to BCL
serialisation classes, it has a single <code>Emit</code> method which takes
in a variety of objects.</p>
<h2 id="writing-property-value-maps">Writing property value maps</h2>
<p>To create our dictionary map, we start by emitting a
<code>MappingStart</code> object. Of course, if you have a start you need
an end so we'll close by emitting <code>MappingEnd</code>.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
emitter<span class="symbol">.</span>Emit<span class="symbol">(</span><span class="keyword">new</span> MappingStart<span class="symbol">(</span><span class="keyword">null</span><span class="symbol">,</span> <span class="keyword">null</span><span class="symbol">,</span> <span class="keyword">false</span><span class="symbol">,</span> MappingStyle<span class="symbol">.</span>Block<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>

<span class="comment">// reset of serialisation code</span>

emitter<span class="symbol">.</span>Emit<span class="symbol">(</span><span class="keyword">new</span> MappingEnd<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<blockquote>
<p>YAML supports block and flow styles. Block is essentially one
value per line, while flow is a more condensed comma separated
style. Block is much more readable for complex objects, but
flow is probably more valuable for short lists of simple
values.</p>
</blockquote>
<p>Next we need to write our key value pairs, which we do by
emitting pairs of <code>Scalar</code> objects.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">if</span> <span class="symbol">(</span>node<span class="symbol">.</span>Name <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
<span class="symbol">{</span>
 emitter<span class="symbol">.</span>Emit<span class="symbol">(</span><span class="keyword">new</span> Scalar<span class="symbol">(</span><span class="keyword">null</span><span class="symbol">,</span> <span class="string">&quot;Name&quot;</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 emitter<span class="symbol">.</span>Emit<span class="symbol">(</span><span class="keyword">new</span> Scalar<span class="symbol">(</span><span class="keyword">null</span><span class="symbol">,</span> node<span class="symbol">.</span>Name<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">if</span> <span class="symbol">(</span>node<span class="symbol">.</span>Title <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
<span class="symbol">{</span>
 emitter<span class="symbol">.</span>Emit<span class="symbol">(</span><span class="keyword">new</span> Scalar<span class="symbol">(</span><span class="keyword">null</span><span class="symbol">,</span> <span class="string">&quot;Title&quot;</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 emitter<span class="symbol">.</span>Emit<span class="symbol">(</span><span class="keyword">new</span> Scalar<span class="symbol">(</span><span class="keyword">null</span><span class="symbol">,</span> node<span class="symbol">.</span>Title<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<blockquote>
<p>Although the YAML specification allows for null values,
attempting to emit a <code>Scalar</code> with a null value seems to
destabilise the emitter and it will promptly crash on
subsequent calls to <code>Emit</code>. For this reason, in the code above
I wrap each pair in a null check. (Not to mention if it is a
null value there is probably no need to serialise anything
anyway).</p>
</blockquote>
<h2 id="writing-lists">Writing lists</h2>
<p>With the basic properties serialised, we can now turn to our
child collections.</p>
<p>This time, after writing a single <code>Scalar</code> with the property
name instead of writing another <code>Scalar</code> we use the
<code>SequenceStart</code> and <code>SequenceEnd</code> classes to tell YamlDotNet
we're going to serialise a list of values.</p>
<p>For our <code>Topics</code> property, the values are simple strings so we
can just emit a <code>Scalar</code> for each entry in the list.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">if</span> <span class="symbol">(</span>node<span class="symbol">.</span>HasTopics<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>WriteTopics<span class="symbol">(</span>emitter<span class="symbol">,</span> node<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">private</span> <span class="keyword">void</span> WriteTopics<span class="symbol">(</span>IEmitter emitter<span class="symbol">,</span> ContentCategory node<span class="symbol">)</span>
<span class="symbol">{</span>
 emitter<span class="symbol">.</span>Emit<span class="symbol">(</span><span class="keyword">new</span> Scalar<span class="symbol">(</span><span class="keyword">null</span><span class="symbol">,</span> <span class="string">&quot;Topics&quot;</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 emitter<span class="symbol">.</span>Emit<span class="symbol">(</span><span class="keyword">new</span> SequenceStart<span class="symbol">(</span><span class="keyword">null</span><span class="symbol">,</span> <span class="keyword">null</span><span class="symbol">,</span> <span class="keyword">false</span><span class="symbol">,</span> SequenceStyle<span class="symbol">.</span>Block<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">foreach</span> <span class="symbol">(</span><span class="keyword">string</span> child <span class="keyword">in</span> node<span class="symbol">.</span>Topics<span class="symbol">)</span>
 <span class="symbol">{</span>
 emitter<span class="symbol">.</span>Emit<span class="symbol">(</span><span class="keyword">new</span> Scalar<span class="symbol">(</span><span class="keyword">null</span><span class="symbol">,</span> child<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 emitter<span class="symbol">.</span>Emit<span class="symbol">(</span><span class="keyword">new</span> SequenceEnd<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>As the <code>Categories</code> property returns a collection of
<code>ContentCategory</code> objects, we can simply start a new list as we
did for topics and then recursively call <code>WriteYaml</code> to write
each child category object in the list.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">if</span> <span class="symbol">(</span>node<span class="symbol">.</span>HasCategories<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>WriteChildren<span class="symbol">(</span>emitter<span class="symbol">,</span> node<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">private</span> <span class="keyword">void</span> WriteChildren<span class="symbol">(</span>IEmitter emitter<span class="symbol">,</span> ContentCategory node<span class="symbol">)</span>
<span class="symbol">{</span>
 emitter<span class="symbol">.</span>Emit<span class="symbol">(</span><span class="keyword">new</span> Scalar<span class="symbol">(</span><span class="keyword">null</span><span class="symbol">,</span> <span class="string">&quot;Categories&quot;</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 emitter<span class="symbol">.</span>Emit<span class="symbol">(</span><span class="keyword">new</span> SequenceStart<span class="symbol">(</span><span class="keyword">null</span><span class="symbol">,</span> <span class="keyword">null</span><span class="symbol">,</span> <span class="keyword">false</span><span class="symbol">,</span> SequenceStyle<span class="symbol">.</span>Block<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">foreach</span> <span class="symbol">(</span>ContentCategory child <span class="keyword">in</span> node<span class="symbol">.</span>Categories<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>WriteYaml<span class="symbol">(</span>emitter<span class="symbol">,</span> child<span class="symbol">,</span> _contentCategoryNodeType<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 emitter<span class="symbol">.</span>Emit<span class="symbol">(</span><span class="keyword">new</span> SequenceEnd<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="deserialisation">Deserialisation</h2>
<p>In this article, I'm only covering custom serialisation.
However, the beauty of this code is that it doesn't generate
different YAML from default serialisation, it only excludes
values that it knows are defaults or that can't be read back,
and provides custom ordering of values. This means you can use
the basic deserialisation code presented at the start of this
article and it will just work, as demonstrated by the sample
program accompanying this post.</p>
<p>For this reason, for the time being I change the <code>ReadYaml</code>
method of our custom type converter to throw an exception
instead of actually doing anything.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">object</span> ReadYaml<span class="symbol">(</span>IParser parser<span class="symbol">,</span> Type type<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> NotImplementedException<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="using-the-custom-type-converter">Using the custom type converter</h2>
<p>Now we have a functioning type converter, we need to tell
YamlDotNet about it.</p>
<p>At the start of the article, I showed how you create a
<code>SerializerBuilder</code> object and call its <code>Build</code> method to get a
configured <code>Serializer</code> class. By calling the builder
objects<code>WithTypeConverter</code> method, we can enable the use of our
custom converter.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
Serializer serializer<span class="symbol">;</span>
<span class="keyword">string</span> yaml<span class="symbol">;</span>

serializer <span class="symbol">=</span> <span class="keyword">new</span> SerializerBuilder<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">.</span>WithTypeConverter<span class="symbol">(</span><span class="keyword">new</span> ContentCategoryYamlTypeConverter<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">.</span>Build<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

yaml <span class="symbol">=</span> serializer<span class="symbol">.</span>Serialize<span class="symbol">(</span>_categories<span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<p>See the attached demonstration program for a fully working sample.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2017-04-01 - First published</li>
<li>2020-11-22 - Updated formatting</li>
</ul>

<p><small>
All content <a href="https://devblog.cyotek.com/copyright-and-trademarks">Copyright (c) by Cyotek Ltd</a> or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.<br />Original URL of this content is https://devblog.cyotek.com/post/using-custom-type-converters-with-csharp-and-yamldotnet-part-1 .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.com