Type metadata in JSON is apparently not honored if $type is not the first property in the object

我们两清 提交于 2020-01-15 08:39:29

问题


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:

I assume that since the error message is :

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


回答1:


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.




回答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);
    }


来源:https://stackoverflow.com/questions/32958062/type-metadata-in-json-is-apparently-not-honored-if-type-is-not-the-first-proper

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!