SamboyCoding / Tomlet

Zero-Dependency, model-based TOML De/Serializer for .NET

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Deserializing into derived class does not deserialize into members of base class if they are declared as properties

levicki opened this issue · comments

Consider this code:

using Tomlet;

public class TableA
{
	public int	IntA	{ get; set; }
	public string	StringA	{ get; set; }
}

public class TableB
{
	public int	IntB	{ get; set; }
	public string	StringB	{ get; set; }
}

public abstract class Base
{
	public TableA	A	{ get; set; }
	public TableB	B	{ get; set; }
	public string	Junk;
}

public class TableC
{
	public int	IntC	{ get; set; }
	public string	StringC	{ get; set; }
}

public class Derived : Base
{
	public TableC	C	{ get; set; }
}

Derived Config = TomletMain.To<Derived>(File.ReadAllText("config.toml"));

And this config file:

Junk = "Whatever"

[A]
IntA = 42
StringA = "Answer"

[B]
IntB = 33
StringB = "LongPlay"

[C]
IntC = 45
StringC = "Single"

In my tests, Junk is deserialized, A and B are not deserialized if they are declared as properties (i.e. with get and set), only if they are declared as fields, Finally, C is always deserialized, property or not.

Am I doing something wrong or unexpected here or is that a bug?

EDIT: Edited to remove semi-colons from TOML snippet, they were not supposed to be there, sorry.

It's not as clearly documented as it perhaps could be, but this is expected behavior. TOML serializes fields, not properties, but if you want to serialize a property you can use the [TomlProperty("name")] attribute on the property declaration.

@SamboyCoding Thank you for taking a look at the issue.

Please allow me make it perfectly clear (because you used word "serializes" in your response) that I am talking about deserializing here.

[TomlProperty] is documented well enough for me, I saw it mentioned in the README.

Do you mean to add the attribute like this?

	[TomlProperty("A")]
	public TableA	A	{ get; set; }
	[TomlProperty("B")]
	public TableB	B	{ get; set; }

Because I have tried that already before creating this issue, and it didn't work.

From my tests, your parser does not seem to have any problems deserializing built-in types (i.e. string, int, ...) when they are declared as properties — it only chokes on classes declared as properties (which my sample code above should be demonstrating clearly enough).

Granted, it's not really necessary for those classes to be properties in my example, but Visual Studio itself is always suggesting using properties over fields because you can change implementation of the property if needed or make it read only, and with the field you are stuck giving direct full access unless you want to write getter and setter methods in which case why not use the properties to begin with.

With that said, I can understand if you are not interested in supporting this scenario but I think code should follow the principle of least astonishment — in this particular case either no properties should be deserialized or all of them should be deserialized.

Finally, I compared your parser with several other parsers and yours I like the best. Others do not support easy deserialization. It would be great if you could add support for serialization from and deserialization into ExpandoObject.

When I say serializing I mean both that and deserializing - the logic is just a mirror. If you've confirmed that that is the case and that specifically properties do not work with user-defined managed data types, then yes that is a bug and one I will look at.

I will at the same time check what ExpandoObject is and take a further look. Thanks for the feedback!

@SamboyCoding

If you've confirmed that that is the case and that specifically properties do not work with user-defined managed data types, then yes that is a bug and one I will look at.

To clarify, I only confirmed it doesn't work when deserializing — I didn't test serializing with that scenario because so far I only needed parsing a TOML file, not creating one. I appreciate you taking the time to look into it.

I will at the same time check what ExpandoObject is and take a further look.

TL;DR version is that it is a dynamic object whose properties can be created at runtime:

dynamic v = new ExpandoObject();
v.IntA = 42;

There's also a DynamicObject which offers more control over setting and getting members (for example it allows you to make properties case-insensitive, etc).

Thanks for the feedback!

You are welcome.

Ok so a few findings

  • the toml document you gave me is actually invalid due to the semicolons in it, but after removing them I can reproduce your problem
  • the problem is due to the way inheritance works and the way the fields are currently parsed in Tomlet.
  • I'm working on a solution now.

Fixed in version 4.0.0

@SamboyCoding

  • the toml document you gave me is actually invalid due to the semicolons in it, but after removing them I can reproduce your problem

Oh man, sorry... no idea how those sneaked in there. They were definitely not supposed to be there. I have edited the original post to fix it and noted the change at the bottom.

Fixed in version 4.0.0

That's great, looking forward to testing it.

@SamboyCoding

  • Also, deserializing an incorrect type to a property now throws a TomlPropertyTypeMismatchException instead of a TomlFieldTypeMismatchException.

This particular change seems to be causing some side effects.

I have joined your Discord server and I will send you a minimal solution which demonstrates the problem.

This ^ basically ended up being a duplicate of #2

@SamboyCoding My apologies for the confusion. I hope nullable support can be added soon because it would be very useful.