TypeHandling and Generics

Jun 20, 2010 at 10:48 PM

The "TypeNameHandling" attribute can normally be used to handle situations where a property will contain types other than the exact one defined (subtypes, or implementations if the defined type is an interface). 

Normally, you would just set this to "Arrays" or "Objects" if you know what you're doing, or "All" if you're mucking around and impatient like me:

        [Newtonsoft.Json.JsonProperty(TypeNameHandling = Newtonsoft.Json.TypeNameHandling.All)]
        public MyInterfaceType TestMember { get; set; }


This does not work with generics though:

        [Newtonsoft.Json.JsonProperty(TypeNameHandling = Newtonsoft.Json.TypeNameHandling.All)]
        public Dictionary<int, List<MyInterfaceType>> AnotherTestMember { get; set; }


In this latter case deserialization still fails. To make it work I need to set the type handling (to All) at the JsonSerializer level, which bloats my file and (I suspect) slows down the processing.

Is there any way to make this work without setting the type handling at the JsonSerializer level?

Thanks for any help!

Jun 21, 2010 at 12:04 AM

That is a bug. It should work. Are you setting any other options when deserializing?

Jun 21, 2010 at 10:14 PM

I'll get some reproduction code together tomorrow, I may have misunderstood the problem. (I know setting the typehandling to all at the serializer level fixed it, when setting the property-specific attribute had not, but I will need to step through more carefully and segregate the problem case)

Jun 22, 2010 at 10:03 PM

In order to upload the sample file (v small vs2008 solution, with 3 classes and a small winforms demo form illustrating the issue), I created an "Issue". Please tell me if you'd rather I send you the file directly somewhere, am happy to do that!

My apologies in advance if I'm doing something stupid :)


Jun 23, 2010 at 4:25 PM

Sorry, just one more note on this: If you could confirm how it's supposed to work, I can get the source and debug, suggest a fix, etc.

Based on your response above, I assume that member-level "jsonproperty" attributes are supposed to override equivalent serializer-level settings - the bit I'm not sure about is how these member-level settings propagate:

  • Should they apply to all "nested" / "contained" objects , or
  • Should they only apply to any nested generic collections where you don't have the option of explicitly specifying the handling because you don't control the definition of the generic collections?

I'll try to download the JSON.Net source and play about a bit tonight

Jun 24, 2010 at 12:23 AM

OK, so having played with the code a bit I don't think I'm qualified to suggest a patch - the "Bug", if there is one, is that there is no concept of Type Handling Inheritance or trickle-down at all. To implement something like this, we would need to differentiate between JsonProperty properties that are strictly single-level (eg "PropertyName", "IValueProvider)) and JsonProperty properties that should trickle-down ("NullValueHandling", "DefaultValueHandling", "DefaultValueHandling", "DefaultValueHandling" - all the "Handling" properties maybe?), and pass them around in different objects instead of the current single JsonProperty "member" object.

Another possible approach would be to add a new "TypeHandling" property for the "JsonObject/JsonObjectContract" - but that would mean the base class would be responsible for deciding if subclasses could be effectively deserialized or not - and that is unpleasantly reminiscent of "XmlInclude".

A third possibility is an "Auto" type handling, where the type is included when it does not match the declared property type; this would probably be the easiest to code and to use... but it might have a performance impact? The JsonContract object would need to start tracking not only the "UnderlyingType" but also the "DeclaredType".

I'll have a go at this third option, see if I can make it work (unless I hear back in the meantime :) )


Jun 24, 2010 at 10:40 AM

I've checked out your example - that is definitely an interesting issue.

I'm not a fan of inheritance but I like the idea of Auto. Don't worry about performance. Comparing types is fast and usefulness is more important than a small amount of performance.

Another approach is putting a type handling option on JsonObjectAttribute/JsonPropertyAttribute for child collection values to use.

I'm not doing another Json.NET release for another couple of months but I'd been interested in seeing what you come up with.

Jun 26, 2010 at 6:47 PM

Alright, so I just uploaded a (simple) patch for the "Auto" approach.

Some general comments with respect to the change:

  • I originally thought it would make sense to implement this using an extra "DeclaredType" property in the "JsonContract" class, but I gave up on that approach because it changes too much code (including an interface) and I'm honestly not sure exactly what the semantics of the "JsonContract" class are - sometimes it seems to be based on the value type (during serialization), and sometimes the member/definition type (during deserialization), so my proposed change would only have made sense half the time (if at all)
  • The existing deserialization mechanism was already essentially "Auto": unless set to "None", the deserialization process doesn't care which of the "TypeNameHandling" options you choose.
  • The solution is slightly "hack"-ish in that it's implemented by creating fake "JsonProperty" objects for collection value types - but the JsonProperty default values are already well chosen (do nothing special unless explicitly set), so it seems to work well.
  • Performance with the change is stellar (in my sample data set, of course - because I had to set "All" for correct behaviour before):
    • Filesize Halved (when indented) or better (when not indented)
    • Serialization is 90% faster, beating the pants off any other method I have tried except BinaryFormatter (including XmlSerializer and SharpSerializer).
    • Deserialization time 120% faster, now faster than any other method except BinaryFormatter.
    • Even though the BinarySerializer file is slightly smaller than a non-indented JSON file, when compressed (with plain vanilla GZip) the Binary file only shrinks by 20%, vs Json's 95% shrinkage.

Basically, with this change the choice of persistence mechanism is a total no-brainer for me (even though I had already chosen JSON.Net anyway :) )



Jul 3, 2010 at 9:56 AM

TypeNameHandling.Auto added.

Jul 4, 2010 at 12:26 PM
Cool, thanks! I now think I might have a similar issue with JsonProperty "IsReference" named parameter (and collection values), but I'll investigate some more and create a separate discussion topic if appropriate.
Jul 4, 2010 at 10:42 PM

In the change just made I added a new "JsonContract collectionValueContract" parameter to most overloads. I'm thinking now it could be changed to pass around the collection contract. Attributes could then be added for collection level handling of settings and read from that collection contract. That is how I'll tackle this eventually.