aaubry / YamlDotNet

YamlDotNet is a .NET library for YAML
MIT License
2.48k stars 466 forks source link

Serilizing an object with tags similar to AWS Cloudformation #911

Closed gitisz closed 1 month ago

gitisz commented 4 months ago

I'm trying to use the C# YamlDotNet to return a serialized output of an object expressed as YAML, where much of the YAML will have tags similiar to those avaialble in AWS Cloudformation templates, such as !FindInMap as documented (here)[https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-findinmap.html].

Here is one example where !FindInMap may be expressed:

value:
 - -Djob.enebled=true
 - !FindInMap [ "FOO", "BAR", "BAZ" ]

Or something slightly more complex, like a nested tag:

 value:
 - -Djob.enebled=true
 - !FindInMap [ "FOO", "BAR", !Ref Baz ]

Using YamlDotNet, I see how to leverage SerializeBuilder, but WithTagMapping eludes me. In the examples above, how might a class/type look to achieve serialize to achieve the same?

Thanks

EdwardCooke commented 4 months ago

With the current implementation of mapping this is probably going to get you as close as you can get. With a little more work you could probably get the first element in your value object list to output without a single quote another type converter. Maybe something like an argument object or something. But, here you go to get you started.

using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;

var serializer = new SerializerBuilder()
    .WithTagMapping("!FindInMap", typeof(List<object>))
    .WithTagMapping("!Ref", typeof(Baz))
    .WithDefaultScalarStyle(ScalarStyle.DoubleQuoted)
    .WithTypeConverter(new BazTypeConverter())
    .Build();

var list = new List<object>();
list.AddRange(new[] { "FOO", "BAR" });
list.Add(new Baz());

var outer = new Outer
{
    Value =
    [
        "-Djob.enebled=true",
        list
    ]
};

var yaml = serializer.Serialize(outer);
Console.WriteLine(yaml);

var deserializer = new DeserializerBuilder()
    .WithTagMapping("!FindInMap", typeof(List<object>))
    .WithTagMapping("!Ref", typeof(Baz))
    .WithTypeConverter(new BazTypeConverter())
    .Build();

Console.WriteLine("=== Deserialized");
var result = deserializer.Deserialize<Outer>(yaml);
foreach (var o in result.Value)
{
    if (o is List<object> list1)
    {
        foreach (var o2 in list1)
        {
            Console.WriteLine($"  {o2}");
        }
    }
    else
    {
        Console.WriteLine(o);
    }
}

class Outer
{
    public object[] Value { get; set; }
}

class BazTypeConverter : IYamlTypeConverter
{
    public bool Accepts(Type type) => type == typeof(Baz);

    public object? ReadYaml(IParser parser, Type type)
    {
        var valid = parser.TryConsume<Scalar>(out var value);
        if (!valid || value == null)
        {
            throw new Exception($"Invalid type, expected scalar got {value?.GetType()}");
        }
        if (value.Value != "Baz")
        {
            throw new Exception("Invalid Scalar Value");
        }
        return new Baz();
    }

    public void WriteYaml(IEmitter emitter, object? value, Type type)
    {
        emitter.Emit(new Scalar(null, "!Ref", "Baz", ScalarStyle.Plain, false, false));
    }
}
class Baz
{
    public override string ToString()
    {
        return "I'm a Baz";
    }
}

Results in

Value:
- "-Djob.enebled=true"
- !FindInMap
  - "FOO"
  - "BAR"
  - !Ref Baz

=== Deserialized
-Djob.enebled=true
  FOO
  BAR
  I'm a Baz
EdwardCooke commented 4 months ago

Did the above answer your question?

EdwardCooke commented 1 month ago

Haven't heard back in a couple of months, I'm going to close the issue.

gitisz commented 1 month ago

Thank you for the generous example! I did shelve this and used a client-side NodeJS alternative, but that was temporary. I'll give this a go and share back. Thank you!