90

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

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

    public ISomething Thing { get; set; }
}

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

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

4

9 回答 9

95

One of the things you can do with Json.NET is:

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

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

The TypeNameHandling flag will add a $type property to the JSON, which allows Json.NET to know which concrete type it needs to deserialize the object into. This allows you to deserialize an object while still fulfilling an interface or abstract base class.

The downside, however, is that this is very Json.NET-specific. The $type will be a fully-qualified type, so if you're serializing it with type info,, the deserializer needs to be able to understand it as well.

Documentation: Serialization Settings with Json.NET

于 2010-03-06T00:13:40.287 回答
53

您可以通过使用 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 类型。

于 2010-11-04T03:47:18.080 回答
39

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

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; }
}
于 2011-06-10T08:30:21.983 回答
25

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

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。这也允许您将您的设置器设为私有。

于 2013-02-28T23:41:49.420 回答
18

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

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

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

于 2012-02-28T16:16:04.003 回答
4

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

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

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

此博客文章中提供了源代码和概述。

于 2012-08-30T18:05:58.690 回答
2

我只是想完成上面@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 的一致性:在此处输入图像描述

于 2017-06-15T18:32:08.350 回答
-5

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

让我们这样看。您将一串数据和一个要反序列化的类型交给 JSon.net。当 JSON.net 遇到 ISomething 时该怎么办?它无法创建新类型的 ISomething,因为 ISomething 不是对象。它也无法创建实现 ISomething 的对象,因为它不知道应该使用可能继承 ISomething 的众多对象中的哪一个。接口,是可以自动序列化但不能自动反序列化的东西。

我会做的是考虑用基类替换 ISomething。使用它,您可能可以获得您正在寻找的效果。

于 2010-02-15T20:58:47.207 回答
-9

这里参考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 序列化接口的具体类。

于 2010-02-12T21:08:41.167 回答