I have a .NET class which represents a RPC method call, like this:
class MethodCall
{
public string MethodName { get; set; }
public Collection<object> Arguments { get; set; }
}
I want to serialize a Collection<MethodCall>
to YAML. I'm using YamlDotNet to achieve this.
By default, YamlDotNet will serialize these objects like this:
methodName: someName
arguments:
- arg1
- arg2
- ...
I would like to simplify the resulting YAML to:
someName:
- arg1
- arg2
Is there any easy way to achieve this? Please note that the arguments can be complex objects (i.e. not simple scalars).
You can achieve this by registering an implementation of IYamlTypeConverter
that performs the conversion that you need.
Here's a possible implementation:
public sealed class MethodCallConverter : IYamlTypeConverter
{
// Unfortunately the API does not provide those in the ReadYaml and WriteYaml
// methods, so we are forced to set them after creation.
public IValueSerializer ValueSerializer { get; set; }
public IValueDeserializer ValueDeserializer { get; set; }
public bool Accepts(Type type) => type == typeof(MethodCall);
public object ReadYaml(IParser parser, Type type)
{
parser.Consume<MappingStart>();
var call = new MethodCall
{
MethodName = (string)ValueDeserializer.DeserializeValue(parser, typeof(string), new SerializerState(), ValueDeserializer),
Arguments = (Collection<object>)ValueDeserializer.DeserializeValue(parser, typeof(Collection<object>), new SerializerState(), ValueDeserializer),
};
parser.Consume<MappingEnd>();
return call;
}
public void WriteYaml(IEmitter emitter, object value, Type type)
{
emitter.Emit(new MappingStart());
var call = (MethodCall)value;
ValueSerializer.SerializeValue(emitter, call.MethodName, typeof(string));
ValueSerializer.SerializeValue(emitter, call.Arguments, typeof(Collection<object>));
emitter.Emit(new MappingEnd());
}
}
The converter needs to be registered into the SerializerBuilder
and DeserializerBuilder
through the WithTypeConverter
method. Note that YamlDotNet does not provide us with a way to call the (de)serializer recursively, so we have to set some public properties as a workaround. This is not as clean as it could be, but still works:
string SerializeMethodCall(MethodCall call)
{
var methodCallConverter = new MethodCallConverter();
var serializerBuilder = new SerializerBuilder()
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.WithTypeConverter(methodCallConverter);
methodCallConverter.ValueSerializer = serializerBuilder.BuildValueSerializer();
var serializer = serializerBuilder.Build();
var yaml = serializer.Serialize(call);
return yaml;
}
MethodCall DeserializeMethodCall(string yaml)
{
var methodCallConverter = new MethodCallConverter();
var deserializerBuilder = new DeserializerBuilder()
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.WithTypeConverter(methodCallConverter);
methodCallConverter.ValueDeserializer = deserializerBuilder.BuildValueDeserializer();
var deserializer = deserializerBuilder.Build();
var call = deserializer.Deserialize<MethodCall>(yaml);
return call;
}