C# 使用 json.net 反序列化没有类型信息的多态 json 类
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19307752/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Deserializing polymorphic json classes without type information using json.net
提问by Peter Kneale
This Imgur apicall returns a list containing both Gallery Imageand Gallery Albumclasses represented in JSON.
这个Imgur api调用返回一个列表,其中包含以 JSON 表示的Gallery Image和Gallery Album类。
I can't see how to deserialize these automatically using Json.NET given that there is no $type property telling the deserializer which class is meant to be represented. There is a property called "IsAlbum" that can be used to differentiate between the two.
我看不出如何使用 Json.NET 自动反序列化这些,因为没有 $type 属性告诉反序列化器要表示哪个类。有一个名为“IsAlbum”的属性可用于区分两者。
Thisquestion appears to show one method but it looks like a bit of a hack.
这个问题似乎显示了一种方法,但它看起来有点像黑客。
How do I go about deserializing these classes? (using C#, Json.NET).
我如何去反序列化这些类?(使用 C#、Json.NET)。
Sample Data:
样本数据:
Gallery Image
画廊图片
{
"id": "OUHDm",
"title": "My most recent drawing. Spent over 100 hours.",
...
"is_album": false
}
Gallery Album
画廊相册
{
"id": "lDRB2",
"title": "Imgur Office",
...
"is_album": true,
"images_count": 3,
"images": [
{
"id": "24nLu",
...
"link": "http://i.imgur.com/24nLu.jpg"
},
{
"id": "Ziz25",
...
"link": "http://i.imgur.com/Ziz25.jpg"
},
{
"id": "9tzW6",
...
"link": "http://i.imgur.com/9tzW6.jpg"
}
]
}
}
采纳答案by Brian Rogers
You can do this fairly easily by creating a custom JsonConverter
to handle the object instantiation. Assuming you have your classes defined something like this:
您可以通过创建自定义JsonConverter
来处理对象实例化来轻松完成此操作。假设你的类定义如下:
public abstract class GalleryItem
{
public string id { get; set; }
public string title { get; set; }
public string link { get; set; }
public bool is_album { get; set; }
}
public class GalleryImage : GalleryItem
{
// ...
}
public class GalleryAlbum : GalleryItem
{
public int images_count { get; set; }
public List<GalleryImage> images { get; set; }
}
You would create the converter like this:
您可以像这样创建转换器:
public class GalleryItemConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(GalleryItem).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader,
Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
// Using a nullable bool here in case "is_album" is not present on an item
bool? isAlbum = (bool?)jo["is_album"];
GalleryItem item;
if (isAlbum.GetValueOrDefault())
{
item = new GalleryAlbum();
}
else
{
item = new GalleryImage();
}
serializer.Populate(jo.CreateReader(), item);
return item;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer,
object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Here's an example program showing the converter in action:
这是一个显示转换器运行的示例程序:
class Program
{
static void Main(string[] args)
{
string json = @"
[
{
""id"": ""OUHDm"",
""title"": ""My most recent drawing. Spent over 100 hours."",
""link"": ""http://i.imgur.com/OUHDm.jpg"",
""is_album"": false
},
{
""id"": ""lDRB2"",
""title"": ""Imgur Office"",
""link"": ""http://alanbox.imgur.com/a/lDRB2"",
""is_album"": true,
""images_count"": 3,
""images"": [
{
""id"": ""24nLu"",
""link"": ""http://i.imgur.com/24nLu.jpg""
},
{
""id"": ""Ziz25"",
""link"": ""http://i.imgur.com/Ziz25.jpg""
},
{
""id"": ""9tzW6"",
""link"": ""http://i.imgur.com/9tzW6.jpg""
}
]
}
]";
List<GalleryItem> items =
JsonConvert.DeserializeObject<List<GalleryItem>>(json,
new GalleryItemConverter());
foreach (GalleryItem item in items)
{
Console.WriteLine("id: " + item.id);
Console.WriteLine("title: " + item.title);
Console.WriteLine("link: " + item.link);
if (item.is_album)
{
GalleryAlbum album = (GalleryAlbum)item;
Console.WriteLine("album images (" + album.images_count + "):");
foreach (GalleryImage image in album.images)
{
Console.WriteLine(" id: " + image.id);
Console.WriteLine(" link: " + image.link);
}
}
Console.WriteLine();
}
}
}
And here is the output of the above program:
这是上述程序的输出:
id: OUHDm
title: My most recent drawing. Spent over 100 hours.
link: http://i.imgur.com/OUHDm.jpg
id: lDRB2
title: Imgur Office
link: http://alanbox.imgur.com/a/lDRB2
album images (3):
id: 24nLu
link: http://i.imgur.com/24nLu.jpg
id: Ziz25
link: http://i.imgur.com/Ziz25.jpg
id: 9tzW6
link: http://i.imgur.com/9tzW6.jpg
Fiddle: https://dotnetfiddle.net/1kplME
回答by xtravar
I'm only posting this to clear up some of the confusion. If you are working with a predefined format and need to deserialize it, this is what I found worked best and demonstrates the mechanics so that others can tweak it as needed.
我发布这个只是为了澄清一些混乱。如果您正在使用预定义的格式并需要对其进行反序列化,这是我发现最有效的方法并演示了机制,以便其他人可以根据需要对其进行调整。
public class BaseClassConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var j = JObject.Load(reader);
var retval = BaseClass.From(j, serializer);
return retval;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
public override bool CanConvert(Type objectType)
{
// important - do not cause subclasses to go through this converter
return objectType == typeof(BaseClass);
}
}
// important to not use attribute otherwise you'll infinite loop
public abstract class BaseClass
{
internal static Type[] Types = new Type[] {
typeof(Subclass1),
typeof(Subclass2),
typeof(Subclass3)
};
internal static Dictionary<string, Type> TypesByName = Types.ToDictionary(t => t.Name.Split('.').Last());
// type property based off of class name
[JsonProperty(PropertyName = "type", Required = Required.Always)]
public string JsonObjectType { get { return this.GetType().Name.Split('.').Last(); } set { } }
// convenience method to deserialize a JObject
public static new BaseClass From(JObject obj, JsonSerializer serializer)
{
// this is our object type property
var str = (string)obj["type"];
// we map using a dictionary, but you can do whatever you want
var type = TypesByName[str];
// important to pass serializer (and its settings) along
return obj.ToObject(type, serializer) as BaseClass;
}
// convenience method for deserialization
public static BaseClass Deserialize(JsonReader reader)
{
JsonSerializer ser = new JsonSerializer();
// important to add converter here
ser.Converters.Add(new BaseClassConverter());
return ser.Deserialize<BaseClass>(reader);
}
}
回答by Abdul Rehman Gill
Following implementation should let you de-serialize without changing the way you have designed your classes and by using a field other than $type to decide what to de-serialize it into.
下面的实现应该让你在不改变你设计类的方式的情况下反序列化,并通过使用 $type 以外的字段来决定将它反序列化成什么。
public class GalleryImageConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(GalleryImage) || objectType == typeof(GalleryAlbum));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
try
{
if (!CanConvert(objectType))
throw new InvalidDataException("Invalid type of object");
JObject jo = JObject.Load(reader);
// following is to avoid use of magic strings
var isAlbumPropertyName = ((MemberExpression)((Expression<Func<GalleryImage, bool>>)(s => s.is_album)).Body).Member.Name;
JToken jt;
if (!jo.TryGetValue(isAlbumPropertyName, StringComparison.InvariantCultureIgnoreCase, out jt))
{
return jo.ToObject<GalleryImage>();
}
var propValue = jt.Value<bool>();
if(propValue) {
resultType = typeof(GalleryAlbum);
}
else{
resultType = typeof(GalleryImage);
}
var resultObject = Convert.ChangeType(Activator.CreateInstance(resultType), resultType);
var objectProperties=resultType.GetProperties();
foreach (var objectProperty in objectProperties)
{
var propType = objectProperty.PropertyType;
var propName = objectProperty.Name;
var token = jo.GetValue(propName, StringComparison.InvariantCultureIgnoreCase);
if (token != null)
{
objectProperty.SetValue(resultObject,token.ToObject(propType)?? objectProperty.GetValue(resultObject));
}
}
return resultObject;
}
catch (Exception ex)
{
throw;
}
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
回答by manuc66
Simply with JsonSubTypesattributes that work with Json.NET
只需使用适用于 Json.NET 的JsonSubTypes属性
[JsonConverter(typeof(JsonSubtypes), "is_album")]
[JsonSubtypes.KnownSubType(typeof(GalleryAlbum), true)]
[JsonSubtypes.KnownSubType(typeof(GalleryImage), false)]
public abstract class GalleryItem
{
public string id { get; set; }
public string title { get; set; }
public string link { get; set; }
public bool is_album { get; set; }
}
public class GalleryImage : GalleryItem
{
// ...
}
public class GalleryAlbum : GalleryItem
{
public int images_count { get; set; }
public List<GalleryImage> images { get; set; }
}
回答by Игорь Орлов
Advanced to Brian Rogersanswer. And about "use Serializer.Populate() instead of item.ToObject()". If derived types has contstructors or some of their has own customconverter you must use general way for deserialize JSON. So you must leave work for instantiate new object to NewtonJson. This way you can achieve it in you CustomJsonConverter:
高级布赖恩罗杰斯回答。关于“使用 Serializer.Populate() 而不是 item.ToObject()”。如果派生类型具有构造函数或其中一些具有自己的自定义转换器,则必须使用通用方法来反序列化 JSON。因此,您必须将实例化新对象的工作留给 NewtonJson。这样您就可以在 CustomJsonConverter 中实现它:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
..... YOU Code For Determine Real Type of Json Record .......
// 1. Correct ContractResolver for you derived type
var contract = serializer.ContractResolver.ResolveContract(DeterminedType);
if (converter != null && !typeDeserializer.Type.IsAbstract && converter.GetType() == GetType())
{
contract.Converter = null; // Clean Wrong Converter grabbed by DefaultContractResolver from you base class for derived class
}
// Deserialize in general way
var jTokenReader = new JTokenReader(jObject);
var result = serializer.Deserialize(jTokenReader, DeterminedType);
return (result);
}
This work if you have recursion of objects.
如果您有对象的递归,则这项工作有效。