问题
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