【问题标题】:Using Json.NET converters to deserialize properties使用 Json.NET 转换器反序列化属性
【发布时间】: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 中的一些新功能,该功能允许您指定没有名称的属性。

标签: .net serialization json.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

【讨论】:

【解决方案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。
    猜你喜欢
    • 2020-06-08
    • 1970-01-01
    • 2021-05-26
    • 1970-01-01
    • 2023-03-12
    • 1970-01-01
    • 2023-04-07
    • 1970-01-01
    相关资源
    最近更新 更多