Search code examples
c#yamlrecordyamldotnet

Why can I not deserialize YAML into a record?


I am using YamlDotNet and I wanted to use records for my deserialized YAML. However, I get an error like this when I try to do so:

Unhandled exception. (Line: 2, Col: 3, Idx: 9) - (Line: 2, Col: 3, Idx: 9): Exception during deserialization

From further experimentation, I discovered record structs, which work completely fine. Regular structs work and so do classes with get and init properties. Clearly I do not know much about records in C#. Out of curiosity, does anybody know what it is about records that prevents them from being deserialized to by YamlDotNet?


Solution

  • tl;dr you'll need a simple workaround, because it's not supported out-of-the-box, see https://github.com/aaubry/YamlDotNet/issues/571

    You need to have paremeter-less (0-arg) constructor in your record for current (circa 13.x) YamlDotNet versions to be able to deserialize. That c-tor will be used solely to create the record in its pre-deserialization state, so you can provide it with normally invalid/unwanted values, like null or other defaults. YamlDotNet will then use "magical" (reflection-based) setting methods anyway, and you can check for those invalid/unwanted values later to verify if the deserialized data actually has everything you need to have a complete record.

    E.g.

    public record Config(
        string someParam,
        string otherParam,
        string yetAnotherParam
    ) {
        public Config() : this(null, null, null) {
        }
    
        public void Validate() {
            if (someParam == null || otherParam == null || yetAnotherParam == null) {
                throw new ArgumentNullException("not all required fields were deserialized properly", (Exception) null);
            }
        }
    
    }
    

    will work just fine, and then you can just call Validate() straight after deserialization if you want (yes, it's not as effective as library-level support for this, but still).

    Alternatively (although I don't really advise to do it), you can e.g.:

    • deserialize YAML to object using YamlDotNet,
    • serialize that object to JSON via System.Text.Json,
    • and finally deserialize that JSON to your target record (via System.Text.Json as well)

    It's both terrible for performance and would require even more obfuscation of the logic in code, but at least can be hidden in a helper method etc.