Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using default deserialization in a custom type converter #1005

Closed
mikhail-barg opened this issue Nov 5, 2024 · 2 comments
Closed

Using default deserialization in a custom type converter #1005

mikhail-barg opened this issue Nov 5, 2024 · 2 comments

Comments

@mikhail-barg
Copy link

I am working on a flexible yaml deserialization where I'd like to have "shortcuts" for some classes.

For example for a class like this

public sealed class HttpSource
{
  [Required] public string url { get; set; };
  public Auth? auth { get; set; }
}

public sealed class Sample 
{
  public HttpSource http_source { get;set; }
}

I'd like to be able to not only write a regular detailed yaml:

http_source: 
  url: "http://sample.com"
  auth: ..

but also a simplified one (when only a url matters):

http_source: "http://sample.com"

This looks like a nice feature in my case, because most of these yamls are going to be written manually by people and I'd like to save them from whiting boilerplate.

So I've come to the following solution:

  1. I'm adding an IMaybeSimpleValue interface and a SimpleValueAttribute attribute (just a marker intreface and attributes):
public interface IMaybeSimpleValue{ }

[AttributeUsage(AttributeTargets.Property]
public sealed class SimpleValueAttribute: Attribute {}
  1. I mark my class with the interface and the "main" property of the class with an attribute:
public sealed class HttpSource: IMaybeSimpleValue
{
  [Required, SimpleValue] public string url { get; set; };
  public Auth? auth { get; set; }
}
  1. I implement a custom type converter:
public sealed class MaybeSimpleValueConverter : IYamlTypeConverter
{
  private readonly IObjectFactory m_objectFactory;

  internal MaybeSimpleValueConverter(IObjectFactory objectFactory)
  {
    this.m_objectFactory = objectFactory;
  }

  bool IYamlTypeConverter.Accepts(Type type)
  {
    return typeof(IMaybeSimpleValue).IsAssignableFrom(type);
  }

  object? IYamlTypeConverter.ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
  {
    if (parser.Current is YamlDotNet.Core.Events.Scalar)
    {
      object value = this.m_objectFactory.Create(type);
      List<PropertyInfo> valueProperties = type.GetProperties().Where(pi => pi.IsDefined(typeof(SimpleValueAttribute))).ToList();
      PropertyInfo pi = valueProperties[0];  //lots of validation skipped
      Scalar scalar = parser.Consume<Scalar>();
      pi.SetValue(value, scalar.Value);
      return value;
    }
    else
    {
      ???? default deserialization goes here!! //return rootDeserializer.Invoke(type);
    }
  }

  void IYamlTypeConverter.WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
  {
    throw new NotSupportedException();
  }
}

So the general idea is that all the classes implementing IMaybeSimpleValue intreface are handled by the type converter. The converter checks if yaml contains a scalar for the object, and if it does then it creates an instance of the object and initializes it's sole required property. (And this does work).

But if the yaml contains a proper object description, it should fall back to the default deserialization routine. And this is where it does not work because I seem to have no way to call a "default" deserialization for a class, without my custom converter.
(If I call rootDeserializer.Invoke(type) it just goes stack overflow because it goes to my type converter again).

So, is there a way to have a "default" deserialization working for my type, without repeating code from ObjectNodeDeserializer

Or is there a simpler way to achieve what I'm aiming for?

@mikhail-barg
Copy link
Author

Okay, I managed to find a way to do this, by using not a type converter, but implementing a custom NodeSerializer and replacing default ScalarNodeDeserializer (where: syntax => syntax.InsteadOf<ScalarNodeDeserializer>()).

Will close the issue then

@mikhail-barg
Copy link
Author

And another, even simpler solution would be just to implement a public static ClassName Parse(string v) method in each class requiring such behavior

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant