如果$ type不是对象

时间:2015-10-05 21:19:08

标签: c# json.net

我查看了有关此主题的问题,the closest to my situation没有解决我的问题。

我有以下课程:

public abstract class BaseClass
{

}

public class ConcreteClass
{

}

序列化和反序列化的设置对象如下:

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

我试图像这样反序列化:

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

但由于某种原因,我收到了错误

  

无法创建BaseClass类型的实例。 Type是接口或抽象类,无法实例化。

即使根Json对象确实具有$type属性。我已尝试反序列化为JObject,然后使用JObject.To<BaseType>(),但我的结果相同。我需要使用这种方法,并且不希望使用自定义转换器,因为我在整个地方使用多态。

您对我如何使这种反序列化工作有任何想法吗?

更新10/10/15

我还在调查,我认为问题可能是当我检查我的反序列化对象的JObject时,第一个属性是_id属性:

enter image description here

我认为因为错误信息是:

enter image description here

JSON.NET可能需要首先读取类型以实例化正确的对象。我不知道如何从一个单独的项目(如下面提供的项目)中重现_id首先出现的情况。我尝试了几种嵌套复杂属性的组合,但我总是首先使用$type。这可能就是它在那里运作良好的原因。

我正试图在ContractResolver上覆盖CreateProperties

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;
    }

但到目前为止,它对JObject中我的属性的顺序没有影响。

更新2

好的,所以我设法通过使用:

$type属性放在最顶层
var prop = deserializedJObject.Property("$type");
deserializedJObject.Remove("$type");
deserializedJObject.AddFirst(prop);

但不幸的是,它没有帮助,我仍然面临同样的演员问题。

更新3

我能够重现这个问题。如果$type属性不是 JSON字符串中的第一个属性,则会发生此错误。这显然是一个错误,因为JSON规范表明属性是无序的。

在我的情况下,我不太了解它,因为JSON对象是由始终将_id置于顶部的数据库返回的。我将在GitHub上记录一个问题,看看我是否可以提出解决方法。

这是一个重现问题的项目:http://we.tl/RiemGkRTF2

2 个答案:

答案 0 :(得分:7)

Json.Net 6.0.3解决了这个问题。来自author's blog

  

元数据属性处理

     

某些Json.NET序列化程序功能(如保留类型或引用)需要Json.NET来读取和写入元数据属性,例如: $ type,$ id和$ ref。由于Json.NET反序列化的工作方式,这些元数据属性必须首先在JSON对象中进行排序。这可能会导致问题,因为无法在JavaScript和其他一些JSON框架中对JSON对象属性进行排序。

     

此版本添加了一个新设置,允许元数据属性位于对象中的任何位置: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
     

在内部,此设置将指示序列化程序将整个JSON对象加载到内存中。然后将从对象中读取元数据属性,然后反复序列化将继续正常进行。内存使用和速度有一点点成本,但是如果你需要一个使用元数据属性但不能保证JSON对象属性顺序的功能,那么你会发现它很有用。

从底线开始,将MetadataPropertyHandling = MetadataPropertyHandling.ReadAhead添加到您的设置中,这样可以解决您的问题。

答案 1 :(得分:1)

你没有将SerializationSettings传递给你对DeserializeObject的调用,所以它试图在没有TypeNameHandling.All的情况下进行操作。

仅供参考,对于将来阅读的人,这是我的代码:

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