-
-
Notifications
You must be signed in to change notification settings - Fork 485
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
Call constructor on empty #443
Comments
It is not too hard to achieve. On the serialization side, you need to add an implementation of public sealed class ForceEmptySequencesOnSerialization : ChainedEventEmitter
{
public ForceEmptySequencesOnSerialization(IEventEmitter nextEmitter) : base(nextEmitter)
{
}
public override void Emit(ScalarEventInfo eventInfo, IEmitter emitter)
{
if (eventInfo.Tag == "tag:yaml.org,2002:null" && IsList(eventInfo.Source.Type))
{
emitter.Emit(new SequenceStart(null, null, false, SequenceStyle.Flow));
emitter.Emit(new SequenceEnd());
}
else
{
base.Emit(eventInfo, emitter);
}
}
private bool IsList(Type type)
{
return typeof(IList).IsAssignableFrom(type)
|| (type.IsInterface && type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IList<>))
|| type.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IList<>));
}
} and register it in the var serializer = new SerializerBuilder()
.WithEventEmitter(inner => new ForceEmptySequencesOnSerialization(inner), s => s.After<TypeAssigningEventEmitter>())
.Build(); I have registered it after On the deserialization side, you need an public sealed class ForceEmptyListsOnDeserialization : INodeDeserializer
{
private readonly IObjectFactory objectFactory = new DefaultObjectFactory();
public bool Deserialize(IParser parser, Type expectedType, Func<IParser, Type, object> nestedObjectDeserializer, out object value)
{
value = null;
if (IsList(expectedType) && parser.Accept<NodeEvent>(out var evt))
{
if (NodeIsNull(evt))
{
parser.SkipThisAndNestedEvents();
value = objectFactory.Create(expectedType);
return true;
}
}
return false;
}
private bool NodeIsNull(NodeEvent nodeEvent)
{
// http://yaml.org/type/null.html
if (nodeEvent.Tag == "tag:yaml.org,2002:null")
{
return true;
}
if (nodeEvent is Scalar scalar && scalar.Style == ScalarStyle.Plain)
{
var value = scalar.Value;
return value == "" || value == "~" || value == "null" || value == "Null" || value == "NULL";
}
return false;
}
private bool IsList(Type type)
{
return typeof(IList).IsAssignableFrom(type)
|| (type.IsInterface && type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IList<>))
|| type.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IList<>));
}
} and register it like this: var deserializer = new DeserializerBuilder()
.WithNodeDeserializer(new ForceEmptyListsOnDeserialization())
.Build(); |
So for deserialization it worked perfectly, for serialization didn't. I didn't mention it but I will warranty that there are no null values in my classes so the But the output of serializing an empty list is |
Ok, I misunderstood what you wanted to achieve. However the same logic applies, but instead of testing |
The problem is After some debugging I figured out that the methods for So I modified my custom event emitter with the following: public override void Emit(SequenceStartEventInfo eventInfo, IEmitter emitter)
{
if (ImplementsIList(eventInfo.Source.Type) && ListIsEmpty(eventInfo.Source.Type, eventInfo.Source.Value))
{
IObjectDescriptor objectDescriptor = new ObjectDescriptor(string.Empty, typeof(string), typeof(string), ScalarStyle.Any);
ScalarEventInfo scalarEventInfo = new ScalarEventInfo(objectDescriptor);
Emit(scalarEventInfo, emitter);
return;
}
base.Emit(eventInfo, emitter);
}
public override void Emit(SequenceEndEventInfo eventInfo, IEmitter emitter)
{
if (ImplementsIList(eventInfo.Source.Type) && ListIsEmpty(eventInfo.Source.Type, eventInfo.Source.Value))
{
return;
}
base.Emit(eventInfo, emitter);
} The problem with this approach is an exception is thrown:
|
I think you need to set the IsPlainImplicit property of ScalarEventInfo to true because the tag is not set. I can't confirm right now, but I will check later. |
Seeting Values: '' I've decided that this is more complex that I expected and I will just not deviate from the yaml specification, clients should write Feel free to solve this issue, thanks! |
Sorry, to interrupt this discussion ;)
This is good coding practices, never allow null for Collections. You don't need to null check, less errors, less problems. And with that YamlDotnet and everything else works as you desires. |
Nope. Deserializer will still set it to |
@mpelesh maybe you have customized it. Can't reproduced that problem. public class EmptyListTest
{
private readonly ITestOutputHelper _outputHelper;
public EmptyListTest(ITestOutputHelper outputHelper)
{
_outputHelper = outputHelper;
}
public class NullList
{
public NullList()
{
Strings = new List<string>() { };
}
public IList<string> Strings { get; private set; }
}
[Fact]
public void Should_not_have_null()
{
var originalList = new NullList();
var serializer = new SerializerBuilder().Build();
var yaml = serializer.Serialize(originalList);
_outputHelper.WriteLine(yaml);
var deserializer = new DeserializerBuilder().Build();
var list = deserializer.Deserialize<NullList>(yaml);
list.Strings.Should().NotBeNull();
list.Strings.Should().HaveCount(0);
}
[Fact]
public void Should_restore_list()
{
var originalList = new NullList();
originalList.Strings.Add("FooBar");
var serializer = new SerializerBuilder().Build();
var yaml = serializer.Serialize(originalList);
_outputHelper.WriteLine(yaml);
var deserializer = new DeserializerBuilder().Build();
var list = deserializer.Deserialize<NullList>(yaml);
list.Strings.Should().HaveCount(1);
list.Strings[0].Should().Be("FooBar");
}
} |
I also ran into the this issue. Particularly nasty since I've fully embraced 'Nullable' as a project option. While I'd love to see YamlDotNet add support for initializing a collection as 'empty' when there are no elements to add to it I found a solution. @DerAlbertCom solution did not work for me. I tried initializing my collections in a constructor and via property initialization. Neither worked, my collections, arrays in this case, were still set to null but I adjusted your solution a little. I did not investigate why, I did find a solution. Use a full property.
I like this solution because it places the responsibility of delivering an empty collection on the class and not the deserializer. Implementing this is a breeze. Visual Studio can convert an auto property to a full property and vice versa. All you need to do is return 'empty' in case of null whatever 'empty' is. Hope this helps. |
So I recently bumped into this. What is interesting, is using a property initializer at some point in the past used to work. Code like this breaks now: public class Test
{
public List<X> Things { get; set; } = new List<X>();
}
I unfortunately cannot say when this change happened, again it was in a very seldom hit codepath. |
Given objects: public class Root {
public MyObjectA ObjA { get; set; } = new MyObjectA();
public MyObjectB ObjB { get; set; } = new MyObjectB();
}
public class MyObjectA {
public string ItemA1 { get; set; }
public string ItemA2 { get; set; }
}
public class MyObjectB {
public string ItemB1 { get; set; }
public string ItemB2 { get; set; }
} And given yaml: # yaml1
---
obj-a:
item-a1: "foo"
item-a2: "bar"
...
# yaml2
---
obj-a:
item-a1: "foo"
item-a2: "bar"
obj-b:
... I would expect the deserializer (using Instead, |
I would too and judging from seanamosw reply to my comment this used to work in the past and now it does not. Perhaps message the authors of the package? |
@aaubry Why not a simpler IsList condition? private static bool IsList(Type type)
{
return typeof(IEnumerable).IsAssignableFrom(type);
} The solution you posted checks |
When you specify an empty object in YAML, like: ``` quality_profiles: ``` This causes that respective object/collection to be assigned `null`. YamlDotNet feature request covering this behavior can be found [here][1]. Fixes #89. [1]: aaubry/YamlDotNet#443
My implementation checks for both |
Due to the nature of how YAML is structured, is it even possible for |
Let's imagine I have the following class:
Given the following yaml file:
Values:
When I deserialize that class the resulting instance of
MyClass
will have a nullValues
, I'd like to have an emptyList<int>
instead. Is there a way to do this with minimal configuration (for both serializing and deserializing)?The text was updated successfully, but these errors were encountered: