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


88

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

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

    public ISomething Thing { get; set; }
}

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

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


我相信你需要指定用于获取/套ISomething属性名称
公羊

我有。我使用的是C#3.5中引入的自动实现属性的简写。msdn.microsoft.com/en-us/library/bb384054.aspx
dthrasher 2010年

4
是不是那个类型。我认为ram是正确的,您仍然需要一个属性名称。我知道这与您的问题无关,但是上面的评论使我觉得我在.NET中缺少一些新功能,该功能允许您指定不带名称的属性。
穆斯先生2012年

Answers:


92

您可以使用Json.NET进行的操作之一是:

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

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

TypeNameHandling标志将为$typeJSON添加一个属性,该属性使Json.NET知道将对象反序列化为哪种具体类型。这使您可以反序列化对象,同时仍然实现接口或抽象基类。

但是,不利的是,这是非常特定于Json.NET的。该$type会是一个完全合格的类型,所以,如果你与类型信息序列化呢,,解串器需要能够理解它。

文档:使用Json.NET进行序列化设置


有趣。我将不得不解决这个问题。不错的提示!
dthrasher 2010年

2
对于Newtonsoft.Json,它的工作原理类似,但是属性为“ $ type”
Jaap 2012年

太简单了!
Shimmy Weitzhandler '17

1
使用时请注意此处可能存在的安全问题TypeNameHandling。有关详细信息,请参见Newtonsoft Json中的TypeNameHandling警告
dbc

昨天我对转换器感到疯狂,这真是越来越好了,谢谢!!!
Horothenic

52

您可以通过使用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反序列化的组织时,所有者属性的基础IPerson将为Tycoon类型。


非常好。我将不得不尝试转换器。
dthrasher

4
如果标签“ [JsonConverter(typeof(TycoonConverter))]”仍在接口列表中,是否仍可以使用?
2014年

40

就像前面提到的那样,您不必使用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; }
}

辉煌。谢谢:)
达伦·杨

5
对于接口或抽象类的集合,属性为“ ItemTypeNameHandling”。例如:[JsonProperty(ItemTypeNameHandling = TypeNameHandling.Auto)]
Anthony F

这次真是万分感谢!
brudert '19

23

在最新版本的第三方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。这也使您可以将设置员设为私有。


6
这应该从屋顶喊!的确,它增加了对具体实现的约束,但是在可以使用它的情况下,它比其他方法简单得多。
Mark Meuer 2013年

3
如果我们有多个具体类型的构造函数多于1个,它将仍然知道吗?
Teoman shipahi 2014年

1
相比于所有您不得不做的令人费解的废话,这个答案是如此的优雅。这应该是公认的答案。不过,在我的情况下,一个警告是我必须在构造函数之前添加[JsonConstructor]才能使它工作....我怀疑仅在您的一个具体构造函数上使用它会解决您的(4岁)问题@Teomanshipahi
nacitar偷到

@nacitarsevaht我现在可以回过头来解决我的问题:)无论如何我什至都不记得它是什么,但是当我重新看一下时,这在某些情况下是一个很好的解决方案。
Teoman shipahi

我们也使用此方法,但是在大多数情况下,我更喜欢使用convert,因为将具体类型与构造函数耦合会首先破坏为属性使用接口的目的!
gabe

19

遇到了同样的问题,所以我想出了自己的使用已知类型参数的Converter。

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

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


1
这个JsonConverter很棒,我使用了它,但是遇到了这样解决的几个问题:-使用JsonSerializer.CreateDefault()代替Populate,因为我的对象具有更深的层次结构。-使用反射来获取构造函数并在Create()方法中实例化它
Aurel

3

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

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

这仅使用Json.Net的默认序列化器实现,同时显式指定具体类型。

源代码和概述可在此博客文章中找到


1
这是一个很好的解决方案。干杯。
JohnMetta

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的方式:在此处输入图片说明


-5

我想知道同样的事情,但恐怕无法完成。

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

我要做的是看用基类替换ISomething。使用它,您可能能够获得想要的效果。


1
我意识到这将“开箱即用”。但是我想知道是否可以使用诸如“ [JsonProperty(typeof(SomethingBase))]”之类的属性来提供具体的类。
dthrasher

那么,为什么不使用上面的代码中的SomethingBase而不是ISomething呢?可以说,我们也正在以错误的方式看待,因为接口不应该在序列化中使用,因为它们只是通过给定类定义通信“接口”。从技术上来说,对接口进行序列化与对抽象类进行序列化是胡说八道。因此,尽管“可以完成”,但我认为它“不应该完成”。
蒂莫西·鲍德里奇

您是否看过Newtonsoft.Json.Serialization命名空间中的任何类?特别是JsonObjectContract类?
约翰尼2010年

-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序列化的接口。


1
您的示例使用JavaScriptSerializer,这是.NET Framework中的类。我正在使用Json.NET作为序列化器。codeplex.com/Json
dthrasher 2010年

3
没有提到原始问题,Json.NET在那里被明确提及。
奥利弗(Oliver)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.