3

I went through the questions on the subject, and the closest to my situation didn't address my concern.

I have the following classes :

public abstract class BaseClass
{

}

public class ConcreteClass
{

}

My setting object for both the serialization and the deserialization is the following one :

JsonSerializerSettings _serializationSettings = new JsonSerializerSettings 
{ 
    NullValueHandling = NullValueHandling.Ignore, 
    TypeNameHandling = TypeNameHandling.All, 
    ContractResolver = new CloudantContractResolver(), 
    ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor, 
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore 
};

I am trying to deserialize like this:

var myDeserializedObject = JsonConvert.DeserializeObject<BaseClass>(jsonString, _serializationSettings);

But for some reason, I am getting the error

Could not create an instance of type BaseClass. Type is an interface or abstract class and cannot be instantiated.

even though the root Json object does have a $type property. I've tried deserializing to a JObject and then using JObject.To<BaseType>(), but I'm having the same result. I need to get this approach working, and would prefer not to use custom converters as I use polymorphism all over the place.

Do you have any idea on how I can get this deserialization working?

Update 10/10/15

I am still investigating, and I think that the issue might be that when I inspect the JObject of my deserialized object, the first property is the _id property:

enter image description here

I assume that since the error message is :

enter image description here

JSON.NET probably needs to read the type first to instantiate the correct object. I don't see how to reproduce this situation where _id is first, from a separate project like the one provided below. I tried, a couple of combination, of nested complex properties, but I always have the $type first. And it is probably why it works fine there.

I am trying to put together an override of CreateProperties on my ContractResolver :

protected override System.Collections.Generic.IList<JsonProperty> CreateProperties(System.Type type, MemberSerialization memberSerialization)
    {
        var properties = base.CreateProperties(type, memberSerialization);

        var propWithDollar = properties.Where(x => x.PropertyName.Contains("$"));
        foreach (var prop in propWithDollar)
        {
            properties.Remove(prop);
            properties.Insert(0, prop);
        }

        return properties;
    }

But so far it has no effect on the order of my properties in the JObject.

Update 2

Ok, so I managed to do put the $type property at the very top by using :

var prop = deserializedJObject.Property("$type");
deserializedJObject.Remove("$type");
deserializedJObject.AddFirst(prop);

But unfortunately it didn't help, I'm still facing the same cast issue.

Update 3

I have been able to reproduce the issue. If the $type property is not the very first property in the JSON string then this error occurs. This is clearly a bug, as the JSON specification indicates that the properties are unordered.

In my situation I don't quite have much control over that, as the JSON object is returned by a database that always puts _id at the top. I'll log an issue on GitHub and see if I can come up with a workaround.

Here is a project that reproduces the issue : http://we.tl/RiemGkRTF2

Community
  • 1
  • 1
tobiak777
  • 3,175
  • 1
  • 32
  • 44
  • 3
    Look up what the `abstract` keyword does... The error message tells everything you need to know! One google search would have solved your problem. – SkryptX Oct 05 '15 at 21:28
  • 1
    ^ and, **if** `ConcreteClass` is derived from `BaseClass`, then `var myDeserializedObject = JsonConvert.DeserializeObject(jsonString)` is all you need. – Arghya C Oct 05 '15 at 21:32
  • 3
    @SkryptX I am trying to get JSON.NET pick the right concrete class. I am surprised you got 2 upvotes for such an out of the point and condescending comment. How could I say that I am using polymorphism "all over the place" if I don't know what an abstract class is ? – tobiak777 Oct 05 '15 at 21:41
  • @ArghyaC I have simplified the example for clarity. I have many concrete classes, and at the time I do the call I do not know which concrete class I am retrieving, so I need to code against the base class – tobiak777 Oct 05 '15 at 21:42
  • Not 100% here, but it looks like using `DeserializeObject` will force deserialization into a `BaseClass`, not based on the internal information in the thing being deserialized. What about trying type-unsafe deserialization (e.g. something that deserializes as `object`), and then cast to `(BaseClass)` – MariusUt Oct 05 '15 at 21:45
  • @MariusUt Thanks for your interest, absolutely, that is what I was thinking too. This is why I tried to use JObject as an intermediary step but the cast failed, hum you gave me an idea, I'll try some variations around the idea of casting to object – tobiak777 Oct 05 '15 at 21:49
  • Just to confirm, the Json string that you have DOES have the "$type" member, yes? – racraman Oct 05 '15 at 21:51
  • @racraman Thank you for your interest ! Yes it does – tobiak777 Oct 05 '15 at 21:52

2 Answers2

9

This problem was addressed in Json.Net 6.0.3. From the author's blog:

Metadata Property Handling

Some Json.NET serializer features like preserving types or references require Json.NET to read and write metadata properties, e.g. $type, $id and $ref. Because of the way Json.NET deserialization works these metadata properties have had to be ordered first in a JSON object. This can cause problems because JSON object properties can't be ordered in JavaScript and some other JSON frameworks.

This release adds a new setting to allow metadata properties to be located anywhere in an object: MetadataPropertyHandling.ReadAhead

string json = @"{
    'Name': 'James',
    'Password': 'Password1',
    '$type': 'MyNamespace.User, MyAssembly'
}";

object o = JsonConvert.DeserializeObject(json, new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.All,
    // $type no longer needs to be first
    MetadataPropertyHandling = MetadataPropertyHandling.ReadAhead
});

User u = (User)o;
Console.WriteLine(u.Name);
// James

Internally this setting will instruct the serializer to load the entire JSON object into memory. Metadata properties will then be read out of the object, and then deserialization will continue as normal. There is a slight cost in memory usage and speed but if you require a feature that uses metadata properties and can't guarantee JSON object property order then you will find this useful.

Bottom line, add MetadataPropertyHandling = MetadataPropertyHandling.ReadAhead to your settings and that should fix your issue.

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
2

You are not passing your SerializationSettings into your call to DeserializeObject, so it's trying to operate without the TypeNameHandling.All.

FYI, and for anybody reading in future, here's my code :

    public abstract class BaseClass
    {
        public string Key;
    }

    public class ConcreteClass : BaseClass
    {

    }


    public void TestFoo()
    {
        ConcreteClass sourceObject = new ConcreteClass (){ Key = "xyz" };

        JsonSerializerSettings _serializationSettings = new JsonSerializerSettings ()
        { 
            NullValueHandling = NullValueHandling.Ignore, 
            TypeNameHandling = TypeNameHandling.All, 
            ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor, ReferenceLoopHandling = ReferenceLoopHandling.Ignore 
        };

        string json = JsonConvert.SerializeObject(sourceObject, _serializationSettings);
        Console.Out.WriteLine ("Json is {0}", json);

        BaseClass resultObject = JsonConvert.DeserializeObject<BaseClass> (json, _serializationSettings);
        Console.Out.WriteLine ("Result is {0}", resultObject);
    }
racraman
  • 4,988
  • 1
  • 16
  • 16
  • Thanks, I am already passing the parameters, but forgot to add it to my sample snippet. I acknowledge your example works, I'll figure out why real case doesn't – tobiak777 Oct 06 '15 at 09:16