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

DefaultValuesHandling not working when serialising dynamic properties #446

Open
dsparkplug opened this issue Oct 29, 2019 · 2 comments
Open
Labels

Comments

@dsparkplug
Copy link
Contributor

The DefaultValuesHandling behaviour is not being applied to dynamic properties.

To reproduce, run the following code on version 8.0.0

private static void Main(string[] args)
{

	dynamic exportModel = new ExpandoObject();

	exportModel.TestStringWithValue = "TestValue";
	exportModel.TestNullString = default(string);
	exportModel.TestBooleanTrue = true;
	exportModel.TestBooleanFalse = default(bool);
	exportModel.TestIntegerWithValue = 99;
	exportModel.TestZeroInteger = default(int);

	string result1;
	var serializerBuilder = new SerializerBuilder();
	var serializer = serializerBuilder.Build();
	using (var sw = new StringWriter())
	{
		serializer.Serialize(sw, exportModel);
		result1 = sw.ToString();
	}

	string result2;
	serializerBuilder = new SerializerBuilder().ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitDefaults);
	serializer = serializerBuilder.Build();
	using (var sw = new StringWriter())
	{
		serializer.Serialize(sw, exportModel);
		result2 = sw.ToString();
	}

	Console.WriteLine(result1);
	Console.WriteLine(result2);

	Console.WriteLine($"Equal={(result1 == result2)}");
}

The output is:

TestStringWithValue: TestValue
TestNullString:
TestBooleanTrue: true
TestBooleanFalse: false
TestIntegerWithValue: 99
TestZeroInteger: 0

TestStringWithValue: TestValue
TestNullString:
TestBooleanTrue: true
TestBooleanFalse: false
TestIntegerWithValue: 99
TestZeroInteger: 0

Equal=True

We could achieve the expected results in version 7.0.0 using the following code:

private static void Main(string[] args)
{

	dynamic exportModel = new ExpandoObject();

	exportModel.TestStringWithValue = "TestValue";
	exportModel.TestNullString = default(string);
	exportModel.TestBooleanTrue = true;
	exportModel.TestBooleanFalse = default(bool);
	exportModel.TestIntegerWithValue = 99;
	exportModel.TestZeroInteger = default(int);

	string result1;
	var serializerBuilder = new SerializerBuilder().EmitDefaults();
	var serializer = serializerBuilder.Build();
	using (var sw = new StringWriter())
	{
		serializer.Serialize(sw, exportModel);
		result1 = sw.ToString();
	}

	string result2;
	serializerBuilder = new SerializerBuilder();
	serializer = serializerBuilder.Build();
	using (var sw = new StringWriter())
	{
		serializer.Serialize(sw, exportModel);
		result2 = sw.ToString();
	}

	Console.WriteLine(result1);
	Console.WriteLine(result2);

	Console.WriteLine($"Equal={(result1 == result2)}");

}

The output (as we would expect) is:

TestStringWithValue: TestValue
TestNullString:
TestBooleanTrue: true
TestBooleanFalse: false
TestIntegerWithValue: 99
TestZeroInteger: 0

TestStringWithValue: TestValue
TestBooleanTrue: true
TestIntegerWithValue: 99

Equal=False

Note that replacing the ExpandoObject in the first code sample with a class with typed properties produces the correct output (same as the output the second code sample).

@dsparkplug
Copy link
Contributor Author

I have also tried deriving from ChainedObjectGraphVisitor and adding it to the builder using WithEmissionPhaseObjectGraphVisitor. The overridden EnterMapping method does to appear to get called for dynamic properties.

@dsparkplug
Copy link
Contributor Author

It seems to work correctly if I override EnterMapping(IObjectDescriptor key, IObjectDescriptor value, IEmitter context) instead of EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, IEmitter context) and compare the value with the default for value.Type rather than key.Type. e.g.

public class TestDefaultValuesObjectGraphVisitor : ChainedObjectGraphVisitor
{
	public TestDefaultValuesObjectGraphVisitor(IObjectGraphVisitor<IEmitter> nextVisitor)
		: base(nextVisitor)
	{
	}

	public override bool EnterMapping(IObjectDescriptor key, IObjectDescriptor value, IEmitter context)
	{
		var defaultValue = value.Type.IsValueType ? Activator.CreateInstance(value.Type) : null; 
		if (Equals(value.Value, defaultValue))
			return false;

		return base.EnterMapping(key, value, context);
	}
}
var serializerBuilder = new SerializerBuilder().WithEmissionPhaseObjectGraphVisitor(x => new TestDefaultValuesObjectGraphVisitor(x.InnerVisitor));

So maybe DefaultValuesObjectGraphVisitor can be modified similarly?

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

No branches or pull requests

2 participants