aaubry / YamlDotNet

YamlDotNet is a .NET library for YAML
MIT License
2.54k stars 477 forks source link

How do I use this with multiple documents of arbitrary types? #848

Closed attilam closed 1 year ago

attilam commented 1 year ago

Given I have a few classes in C# that I want to deserialize into from a file with multiple YAML documents. How would I go about it? The only samples I've found conveniently work on documents with one known type, so they are of no help. The YAML file I work with could come with different documents in arbitrary order and type.

The SDK seems to work with magic functions I have no idea how/why to use and the source is too big to crawl through, I've tried.

It would be cool to have a sample on how to deserialize more complex YAML streams with multiple documents and types to deserialize into.

So I think what I'd need is something where I would set the classes that could come up in the YAML, and it would figure out which one needs to be instantiated for a given document. The sources seem to have some indication that this would be possible, but I'm wrecking my head over how it would work in practice.

E.g.:

public class Foo {
  public string FooA { get; set; }
  public string FooB { get; set; }
}

public class Bar {
  public string BarA { get; set; }
  public string BarB { get; set; }
}
---
BarA: alpha
BarB: beta
---
FooA: alpha
FooB: beta

Edit: For the time being I've hacked together this awful mess (based on some of the samples), which solves my immediate use case, it's awful, and it assumes a specific document order (first a "TrimmingPrefs", then any number of "Pages"), but I need to move on to other stuff, so it'll do 😅

var deserializer = new DeserializerBuilder()
    .WithNamingConvention(CamelCaseNamingConvention.Instance)
    .Build();
var parser = new Parser(new StringReader(yamlFile.text));

parser.Consume<StreamStart>();

while (parser.Accept<DocumentStart>(out var thing))
{
    if (prefs == null)
    {
        prefs = deserializer.Deserialize<TrimmingPrefs>(parser);
        Debug.Log("Got prefs");
    }
    else
    {
        var doc = deserializer.Deserialize<Page>(parser);
        pages.Add(doc);
    }
}
EdwardCooke commented 1 year ago

On your deserializerbuilder try using a type recriminatory based on existing keys and deserialize it to an object. My laptops having a lot of issues so I can’t put together an example code right now. I’ll hunt down the tests that show how to use it in a few minutes.

EdwardCooke commented 1 year ago

https://github.com/aaubry/YamlDotNet/blob/1a73db760dc440569cfbd86d1d7e11d59cfcdb8a/YamlDotNet.Test/Serialization/BufferedDeserialization/UniqueKeyTypeDiscriminatorTests.cs#L38

attilam commented 1 year ago

Thanks, I've updated my script with the version that implements an interface for the discriminator, and it seems to work fine 👍

var deserializer = new DeserializerBuilder()
    .WithNamingConvention(CamelCaseNamingConvention.Instance)
    .WithTypeDiscriminatingNodeDeserializer(options =>
    {
        options.AddUniqueKeyTypeDiscriminator<ITrimmingDoc>(
            new Dictionary<string, Type>()
            {
                { "link", typeof(Page) },
                { "speed", typeof(TrimmingPrefs) }
            });
    })
    .Build();

var parser = new Parser(new StringReader(yamlFile.text));

parser.Consume<StreamStart>();

while (parser.Accept<DocumentStart>(out var thing))
{
    ITrimmingDoc doc = deserializer.Deserialize<ITrimmingDoc>(parser);

    if (doc is TrimmingPrefs trimmingPrefs)
    {
        prefs = trimmingPrefs;
    }
    else if (doc is Page page)
    {
        pages.Add(page);
    }
    else
    {
        Debug.LogError("Unknown YAML doc");
    }
}

edit: copy-pasta mistake 🤦