1

Closed

TypeNameHandling.Auto and Serializing Derived Type

description

Scenario:
public abstract class Entity { ... }
public class Cat : Entity { ... }
public class Dog : Entity { ... }
It would be nice if the following were to happen with TypeNameHandling.Auto:
JsonSerializer serializer = new JsonSerializer();

//I would expect $type for the Cat to be written in this case:
serializer.Serialize{Entity}(new Cat());

//But not in this case:
serializer.Serialize{Cat}(new Cat());
NOTE: Forgive the curly brackets, CodePlex Is eating my less than and greater than characters

The assumption being that if I have some abstraction in the layer that does my serialization, I'm probably going to have that same abstraction on the deserialization. Currently this doesn't work and I'm forced to switch to TypeNameHandling.All to get the $type of the top-level object written so it can be deserialized correctly on the other side.

Thoughts?

file attachments

Closed Mar 24, 2013 at 1:11 AM by JamesNK
I've modified your code but the functionality is the same.

comments

jonstelly wrote Feb 8, 2013 at 4:16 PM

here's a rough initial patch. This breaks 3 array serialization tests from TypeNameHandlingTests:

sdfsdf
SerializeGenericObjectListWithTypeName
SerializeUsingCustomBinder

It looks like my change is forcing object serialization syntax instead of raw array serialization for those cases.

If anyone can take a look, it's probably an obvious fix to get this patch working.

Thanks.

jonstelly wrote Mar 3, 2013 at 12:58 AM

Attached is a completed patch with all tests passing.

It's worth noting that this is somewhat of a breaking change, detailed below. Note the two tests I had to modify to get them passing.

In one case, the test was intentionally serializing null, so the newly-generic argument's type couldn't be inferred from usage (I'd argue this is a good test, but not a practical piece of code you'll see in the real-world)

In the other case, there was a variable declared as an IList that held a List. Since the generic type was inferred as IList, when serializing List, it included the type information. This is something people will see in their code. The impact is:

1) If serializing a single instance and TypeNameHandling.Auto is specified, the type information for the root object in the graph will be included where it wasn't before. This should not break anything, the structure of the payload is the same, just a single additional $type value at the root.

2) If serializing an array/collection, and the type of the variable is object, or some base type of the actual instance, this changes the structure of the output from a simple json array, to a json structure that contains the array in $values. This could be a breaking change if not deserializing the json using json.net, primarily I'd expect this to crop up in JavaScript run in a browser

There are a few options:

1) Document the change, noting that if structure of the json and interoperability with existing code is important, then calling code needs to be changed when using TypeNameHandling.Auto so that exact type information is available at the point where the object graph is serialized, and that the type of the variable needs to match the type of the instance contained

2) Change TypeNameHandling, adding a new enum/flag value for this new behavior. Root = 8 and AutoWithRoot = Auto | Root?

3) Add some additional logic in the Serialize and Deserialize methods, where a few well-known collection types are handled transparently. I.e. if serializing and the variable type is IList but the instance is a List, don't include $type. Then when deserializing, the method is already generic, so if they asked for an IList to be deserialized, return a List.

I can implement 2 or 3. Either seems reasonable. Documenting it as a breaking change sounds less ideal given the wide-spread use of the library. Thoughts?

JamesNK wrote Mar 3, 2013 at 12:58 AM

I've modified your code but the functionality is the same.

** Closed by JamesNK 2/23/2013 8:38 PM

itavantiq wrote May 10, 2013 at 9:40 AM

We are using the 5.0.5 version and we have exactly the same issue. The serialization of a derived type instance does not contain the $type node with the TypeNameHandling.Auto setting and therefore, client is not allowed to recognize with derived type is used.

The issue has been closed on March but we are still in trouve. Any thoughts?

JamesNK wrote May 10, 2013 at 10:50 PM

Use the SerializeObject overload that lets you specify a root object.