使用Json.NET转换器来反序列化属性
我有一个类定义,其中包含一个返回接口的属性。
public class Foo { public int Number { get; set; } public ISomething Thing { get; set; } }
尝试使用Json.NET序列化Foo类给了我一个错误消息,例如“无法创buildtypes为”ISomething“的实例,ISomething可能是一个接口或抽象类。
是否有一个Json.NET属性或转换器,让我指定一个具体的Something
在反序列化过程中使用的类?
Json.NET可以做的一件事是:
var settings = new JsonSerializerSettings(); settings.TypeNameHandling = TypeNameHandling.Objects; JsonConvert.SerializeObject(entity, Formatting.Indented, settings);
TypeNameHandling
标志将为JSON添加一个$type
属性,这允许Json.NET知道需要反序列化对象的具体types。 这允许您反序列化一个对象,同时仍然满足接口或抽象基类。
但是,不利的一面是,这是Json.NET特有的。 $type
将是一个完全限定的types,所以如果你使用types信息序列化,那么解串器也需要能够理解它。
文档: Json.NET的序列化设置
您可以通过使用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将是Tycoontypes。
如前所述,您可以使用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; } }
在第三方Newtonsoft Json转换器的最新版本中,您可以使用与接口属性相关的具体types来设置构造函数。
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; } }
只要东西实现了一些东西,这应该工作。 JSon转换器也不要使用默认的空构造函数,你必须强制它使用包含具体types的构造函数。
PS。 这也可以让你的制定者保密。
有一个相同的问题,所以我想出了我自己的转换器使用已知types的参数。
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}); } }
你可以定义自己的方式来比较和识别转换types,我只使用类名称。
正常情况下,我一直使用的解决scheme与由DanielTbuild议TypeNameHandling
,但在这里我没有控制传入的JSON(所以不能确保它包括一个$type
属性)我已经写了一个自定义转换器,只是让你明确指定具体types:
public class Model { [JsonConverter(typeof(ConcreteTypeConverter<Something>))] public ISomething TheThing { get; set; } }
这只是使用Json.Net默认的序列化器实现,同时显式指定具体的types。
本博客文章提供了源代码和概述。
我只想完成@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如何得到符合的:
我也想知道同样的事情,但恐怕不能做到。
我们来看看这个。 你向JSon.net提交一串数据,并将其反序列化。 什么是JSON.net做什么时,它碰到了什么? 它不能创build一个新的types的东西,因为东西不是一个对象。 它也不能创build一个实现ISomething的对象,因为它没有任何线索可以inheritance它应该使用的许多对象。 接口,可以自动序列化,但不能自动反序列化。
我会做的是看看用基类replaceISomething。 使用它你可能会得到你正在寻找的效果。
这里是对 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序列化接口的具体类。