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

Posted

技术标签:

【中文标题】使用 Json.NET 转换器反序列化属性【英文标题】:Using Json.NET converters to deserialize properties 【发布时间】:2011-01-16 07:50:55 【问题描述】:

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

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 ISomething 不是那种类型。我认为 ram 是对的,您仍然需要一个属性名称。我知道这与您的问题无关,但您上面的评论让我觉得我错过了 .NET 中的一些新功能,该功能允许您指定没有名称的属性。 【参考方案1】:

您可以使用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

【讨论】:

Newtonsoft.Json 的工作方式类似,但属性是“$type” 在使用TypeNameHandling 时注意此处可能存在的安全问题。详情请见TypeNameHandling caution in Newtonsoft Json。【参考方案2】:

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

【讨论】:

如果标签“[JsonConverter(typeof(TycoonConverter))]”在接口列表中,它是否仍然有效?【参考方案3】:

如前所述,您可以使用属性标记该特定接口属性,这样生成的 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; 

【讨论】:

对于接口或抽象类的集合,属性是“ItemTypeNameHandling”。例如: [JsonProperty(ItemTypeNameHandling = TypeNameHandling.Auto)]【参考方案4】:

在最新版本的第三方 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。这也允许您将设置器设为私有。

【讨论】:

这应该从屋顶喊出来!诚然,它对具体实现增加了限制,但在可以使用它的情况下,它比其他方法要简单得多。 如果我们有超过 1 个具有多种具体类型的构造函数,它还会知道吗? 与你必须做的所有令人费解的废话相比,这个答案是如此优雅。这应该是公认的答案。不过,在我的情况下,一个警告是我必须在构造函数之前添加 [JsonConstructor] 才能使其工作......我怀疑仅在一个具体的构造函数上使用它可以解决你的(4 岁)问题@Teomanshipahi @nacitarsevaht 我现在可以回去解决我的问题 :) 反正我什至不记得它是什么,但是当我重新查看时,这对于某些情况来说是一个很好的解决方案。 我们也使用这个,但在大多数情况下我更喜欢转换,因为将具体类型耦合到构造函数首先破坏了使用属性接口的目的!【参考方案5】:

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

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

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

【讨论】:

这个 JsonConverter 很棒,我使用了它,但遇到了一些我通过这种方式解决的问题: - 使用 JsonSerializer.CreateDefault() 代替 Populate,因为我的对象具有更深的层次结构。 - 使用反射检索构造函数并在 Create() 方法中实例化它【参考方案6】:

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

public class Model

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

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

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

【讨论】:

这是一个很好的解决方案。干杯。【参考方案7】:

我只是想完成上面@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 是如何得到一致的

【讨论】:

【参考方案8】:

我也想过同样的事情,但恐怕做不到。

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

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

【讨论】:

我意识到它不会“开箱即用”。但我想知道是否有一些像“[JsonProperty(typeof(SomethingBase))]”这样的属性可以用来提供一个具体的类。 那么为什么不在上面的代码中使用 SomethingBase 而不是 ISomething 呢?可以说我们也以错误的方式看待这个问题,因为接口不应该在序列化中使用,因为它们只是定义了与给定类的通信“接口”。从技术上讲,序列化接口是无稽之谈,序列化抽象类也是如此。因此,虽然它“可以完成”,但我认为它“不应该完成”。 您是否查看过 Newtonsoft.Json.Serialization 命名空间中的任何类?特别是 JsonObjectContract 类?【参考方案9】:

Here is a referenceScottGu 写的一篇文章

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

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

【讨论】:

您的示例使用 JavaScriptSerializer,这是 .NET Framework 中的一个类。我使用 Json.NET 作为我的序列化程序。 codeplex.com/Json 不是指原始问题,那里明确提到了Json.NET。

以上是关于使用 Json.NET 转换器反序列化属性的主要内容,如果未能解决你的问题,请参考以下文章

使属性反序列化但不使用 json.net 序列化

使用 Json.Net 进行 C# 枚举反序列化:将值转换为类型时出错

使用带有 ItemRequired = Required.Always 的 Json.Net 反序列化时忽略属性

如何使用 Json.Net 序列化/反序列化具有附加属性的自定义集合

Json.NET - CustomCreationConverter 中单个属性的默认反序列化行为

Json/XML序列化和反序列化