使用Json.NET转换器反序列化属性

时间:2010-02-12 20:22:44

标签: .net serialization json.net

我有一个类定义,其中包含一个返回接口的属性。

public class Foo
{ 
    public int Number { get; set; }

    public ISomething Thing { get; set; }
}

尝试使用Json.NET序列化Foo类给出了一条错误消息,例如“无法创建类型为'ISomething'的实例.ISomething可能是一个接口或抽象类。”

是否有Json.NET属性或转换器可以让我指定在反序列化期间使用的具体Something类?

9 个答案:

答案 0 :(得分:86)

使用Json.NET可以做的一件事是:

var settings = new JsonSerializerSettings();
settings.TypeNameHandling = TypeNameHandling.Objects;

JsonConvert.SerializeObject(entity, Formatting.Indented, settings);

TypeNameHandling标志将向JSON添加$type属性,这允许Json.NET知道将对象反序列化所需的具体类型。这允许您在仍然满足接口或抽象基类的同时反序列化对象。

然而,缺点是这是特定于Json.NET的。 $type将是完全限定类型,因此如果您使用类型信息对其进行序列化,则解串器也需要能够理解它。

文档:Serialization Settings with Json.NET

答案 1 :(得分:49)

您可以通过使用JsonConverter类来实现此目的。假设您有一个具有接口属性的类;

public class Organisation {
  public string Name { get; set; }

  [JsonConverter(typeof(TycoonConverter))]
  public IPerson Owner { get; set; }
}

public interface IPerson {
  string Name { get; set; }
}

public class Tycoon : IPerson {
  public string Name { get; set; }
}

您的JsonConverter负责序列化和反序列化基础属性;

public class TycoonConverter : JsonConverter
{
  public override bool CanConvert(Type objectType)
  {
    return (objectType == typeof(IPerson));
  }

  public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
  {
    return serializer.Deserialize<Tycoon>(reader);
  }

  public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
  {
    // Left as an exercise to the reader :)
    throw new NotImplementedException();
  }
}

当您使用通过Json.Net反序列化的组织时,Owner属性的基础IPerson将是Tycoon类型。

答案 2 :(得分:37)

如前所述,您不必使用TypeNameHandling.Objects选项将自定义的JsonSerializerSettings对象传递给JsonConvert.SerializeObject(),而只需使用属性标记该特定的接口属性,这样生成的JSON就不会因为“$”而膨胀。在每个对象上键入“属性:

public class Foo
{
    public int Number { get; set; }

    // Add "$type" property containing type info of concrete class.
    [JsonProperty( TypeNameHandling = TypeNameHandling.Objects )]
    public ISomething { get; set; }
}

答案 3 :(得分:20)

在最新版本的第三方Newtonsoft Json转换器中,您可以设置一个具有与interfaced属性相关的具体类型的构造函数。

public class Foo
{ 
    public int Number { get; private set; }

    public ISomething IsSomething { get; private set; }

    public Foo(int number, Something concreteType)
    {
        Number = number;
        IsSomething = concreteType;
    }
}

只要Something实现ISomething,这应该有效。如果JSon转换器尝试使用它,也不要放置默认的空构造函数,必须强制它使用包含具体类型的构造函数。

PS。这也允许您将您的setter设为私有。

答案 4 :(得分:15)

有同样的问题所以我想出了我自己的转换器,它使用已知的类型参数。

public class JsonKnownTypeConverter : JsonConverter
{
    public IEnumerable<Type> KnownTypes { get; set; }

    public JsonKnownTypeConverter(IEnumerable<Type> knownTypes)
    {
        KnownTypes = knownTypes;
    }

    protected object Create(Type objectType, JObject jObject)
    {
        if (jObject["$type"] != null)
        {
            string typeName = jObject["$type"].ToString();
            return Activator.CreateInstance(KnownTypes.First(x =>typeName.Contains("."+x.Name+",")));
        }

        throw new InvalidOperationException("No supported type");
    }

    public override bool CanConvert(Type objectType)
    {
        if (KnownTypes == null)
            return false;

        return (objectType.IsInterface || objectType.IsAbstract) && KnownTypes.Any(objectType.IsAssignableFrom);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // Load JObject from stream
        JObject jObject = JObject.Load(reader);
        // Create target object based on JObject
        var target = Create(objectType, jObject);
        // Populate the object properties
        serializer.Populate(jObject.CreateReader(), target);
        return target;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

我为反序列化和序列化定义了两种扩展方法:

public static class AltiJsonSerializer
{
    public static T DeserializeJson<T>(this string jsonString, IEnumerable<Type> knownTypes = null)
    {
        if (string.IsNullOrEmpty(jsonString))
            return default(T);

        return JsonConvert.DeserializeObject<T>(jsonString,
                new JsonSerializerSettings
                {
                    TypeNameHandling = TypeNameHandling.Auto, 
                    Converters = new List<JsonConverter>
                        (
                            new JsonConverter[]
                            {
                                new JsonKnownTypeConverter(knownTypes)
                            }
                        )
                }
            );
    }

    public static string SerializeJson(this object objectToSerialize)
    {
        return JsonConvert.SerializeObject(objectToSerialize, Formatting.Indented,
        new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto});
    }
}

您可以定义自己的比较和识别转换类型的方式,我只使用类名。

答案 5 :(得分:2)

通常我总是使用DanielT建议的TypeNameHandling解决方案,但在这种情况下,我无法控制传入的JSON(因此无法确保它包含$type属性)我编写了一个自定义转换器,只允许您明确指定具体类型:

public class Model
{
    [JsonConverter(typeof(ConcreteTypeConverter<Something>))]
    public ISomething TheThing { get; set; }
}

这只是使用Json.Net中的默认序列化程序实现,同时明确指定具体类型。

this blog post上提供了源代码和概述。

答案 6 :(得分:1)

我只是想完成@Daniel T.向我们展示的例子:

如果您使用此代码序列化对象:

var settings = new JsonSerializerSettings();
settings.TypeNameHandling = TypeNameHandling.Objects;
JsonConvert.SerializeObject(entity, Formatting.Indented, settings);

反序列化json的代码应如下所示:

var settings = new JsonSerializerSettings(); 
settings.TypeNameHandling = TypeNameHandling.Objects;
var entity = JsonConvert.DeserializeObject<EntityType>(json, settings);

这是使用TypeNameHandling标志时json符合的方式:enter image description here

答案 7 :(得分:-5)

我想知道同样的事情,但我担心它无法完成。

让我们这样看待它。您将一串数据和一个要反序列化的类型交给JSon.net。什么是JSON.net当它击中那个ISomething时要做什么?它无法创建新类型的ISomething,因为ISomething不是对象。它也不能创建一个实现ISomething的对象,因为它没有线索可以继承其应该使用的ISomething的许多对象中的哪一个。接口,可以自动序列化,但不能自动反序列化。

我要做的就是用基类替换ISomething。使用它你可能会得到你正在寻找的效果。

答案 8 :(得分:-9)

Here is a reference来自ScottGu撰写的文章

基于此,我写了一些我认为可能有用的代码

public interface IEducationalInstitute
{
    string Name
    {
        get; set;
    }

}

public class School : IEducationalInstitute
{
    private string name;
    #region IEducationalInstitute Members

    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    #endregion
}

public class Student 
{
    public IEducationalInstitute LocalSchool { get; set; }

    public int ID { get; set; }
}

public static class JSONHelper
{
    public static string ToJSON(this object obj)
    {
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        return serializer.Serialize(obj);
    }
    public  static string ToJSON(this object obj, int depth)
    {
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        serializer.RecursionLimit = depth;
        return serializer.Serialize(obj);
    }
}

这就是你怎么称呼它

School myFavSchool = new School() { Name = "JFK High School" };
Student sam = new Student()
{
    ID = 1,
    LocalSchool = myFavSchool
};
string jSONstring = sam.ToJSON();

Console.WriteLine(jSONstring);
//Result {"LocalSchool":{"Name":"JFK High School"},"ID":1}

如果我理解正确,我认为你不需要指定一个实现JSON序列化接口的具体类。