此 Imgur api 调用返回一个列表,其中包含以 JSON 表示的图库图像和图库相册类。
我看不出如何使用 Json.NET 自动反序列化这些,因为没有$type属性告诉反序列化器要表示哪个类。有一个名为"IsAlbum"的属性可用于区分两者。
这个问题似乎显示了一种方法,但它看起来有点黑客。
如何反序列化这些类?(使用 C#,Json.NET(。
示例数据:
图库图像
{
"id": "OUHDm",
"title": "My most recent drawing. Spent over 100 hours.",
...
"is_album": false
}
图库相册
{
"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"
}
]
}
}
您可以通过创建自定义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; }
}
您将像这样创建转换器:
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();
}
}
下面是一个示例程序,显示了转换器的运行情况:
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();
}
}
}
这是上述程序的输出:
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
小提琴:https://dotnetfiddle.net/1kplME
只需使用 JsonSubType 属性即可 Json.NET
[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; }
}
高级到布莱恩·罗杰斯回答。关于"使用Serializer.Populate((而不是item。ToObject(("。如果派生类型具有结构体或某些类型具有自己的自定义转换器,则必须使用常规方法来反序列化 JSON。因此,您必须将新对象实例化为 NewtonJson 的工作。通过这种方式,您可以在自定义JsonConverter中实现它:
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);
}
如果您有对象的递归,这将起作用。
我发布这个只是为了消除一些困惑。 如果您正在使用预定义的格式并需要反序列化它,这是我发现效果最好的方法,并演示了机制,以便其他人可以根据需要对其进行调整。
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);
}
}
以下实现应该允许您反序列化,而无需更改设计类的方式,并使用 $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();
}
}
@ИгорьОрлов 的答案适用于只能通过 JSON.net 直接实例化的类型(由于[JsonConstructor]
和/或直接在构造函数参数上使用[JsonProperty]
。但是,当 JSON.net 已经缓存了要使用的转换器时,覆盖contract.Converter = null
不起作用。
(如果 JSON.NET 使用不可变类型来指示数据和配置何时不再可变,这将不是问题,勒叹息(
就我而言,我这样做了:
- 实现了一个自定义
JsonConverter<T>
(其中T
是我的 DTO 的基类(。 - 定义了一个
DefaultContractResolver
子类,该子类覆盖ResolveContractConverter
以仅返回基类的自定义JsonConverter
。
详细地,并通过示例:
假设我有这些不可变的 DTO,它们代表一个远程文件系统(所以有 DirectoryDto
和 FileDto
都继承FileSystemDto
,就像 DirectoryInfo
和 FileInfo
如何从System.IO.FileSystemInfo
派生(:
public enum DtoKind
{
None = 0,
File,
Directory
}
public abstract class FileSystemDto
{
protected FileSystemDto( String name, DtoKind kind )
{
this.Name = name ?? throw new ArgumentNullException(nameof(name));
this.Kind = kind;
}
[JsonProperty( "name" )]
public String Name { get; }
[JsonProperty( "kind" )]
public String Kind { get; }
}
public class FileDto : FileSystemDto
{
[JsonConstructor]
public FileDto(
[JsonProperty("name" )] String name,
[JsonProperty("length")] Int64 length,
[JsonProperty("kind") ] DtoKind kind
)
: base( name: name, kind: kind )
{
if( kind != DtoKind.File ) throw new InvalidOperationException( "blargh" );
this.Length = length;
}
[JsonProperty( "length" )]
public Int64 Length { get; }
}
public class DirectoryDto : FileSystemDto
{
[JsonConstructor]
public FileDto(
[JsonProperty("name")] String name,
[JsonProperty("kind")] DtoKind kind
)
: base( name: name, kind: kind )
{
if( kind != DtoKind.Directory ) throw new InvalidOperationException( "blargh" );
}
}
假设我有一个 JSON FileSystemDto
数组:
[
{ "name": "foo.txt", "kind": "File", "length": 12345 },
{ "name": "bar.txt", "kind": "File", "length": 12345 },
{ "name": "subdir", "kind": "Directory" },
]
我想 Json.net 将其反序列化为List<FileSystemDto>
...
因此,定义一个 DefaultContractResolver
的子类(或者如果你已经有一个解析器实现,那么子类(或组合(它(并覆盖ResolveContractConverter
:
public class MyContractResolver : DefaultContractResolver
{
protected override JsonConverter? ResolveContractConverter( Type objectType )
{
if( objectType == typeof(FileSystemDto) )
{
return MyJsonConverter.Instance;
}
else if( objectType == typeof(FileDto ) )
{
// use default
}
else if( objectType == typeof(DirectoryDto) )
{
// use default
}
return base.ResolveContractConverter( objectType );
}
}
然后实现MyJsonConverter
:
public class MyJsonConverter : JsonConverter<FileSystemDto>
{
public static MyJsonConverter Instance { get; } = new MyJsonConverter();
private MyJsonConverter() {}
// TODO: Override `CanWrite => false` and `WriteJson { throw; }` if you like.
public override FileSystemDto? ReadJson( JsonReader reader, Type objectType, FileSystemDto? existingValue, Boolean hasExistingValue, JsonSerializer serializer )
{
if( reader.TokenType == JsonToken.Null ) return null;
if( objectType == typeof(FileSystemDto) )
{
JObject jsonObject = JObject.Load( reader );
if( jsonObject.Property( "kind" )?.Value is JValue jv && jv.Value is String kind )
{
if( kind == "File" )
{
return jsonObject.ToObject<FileDto>( serializer );
}
else if( kind == "Directory" )
{
return jsonObject.ToObject<DirectoryDto>( serializer );
}
}
}
return null; // or throw, depending on your strictness.
}
}
然后,要反序列化,请使用正确设置ContractResolver
的JsonSerializer
实例,例如:
public static IReadOnlyList<FileSystemDto> DeserializeFileSystemJsonArray( String json )
{
JsonSerializer jss = new JsonSerializer()
{
ContractResolver = new KuduDtoContractResolver()
};
using( StringReader strRdr = new StringReader( json ) )
using( JsonTextReader jsonRdr = new JsonTextReader( strRdr ) )
{
List<FileSystemDto>? list = jss.Deserialize< List<FileSystemDto> >( jsonRdr );
// TODO: Throw if `list` is null.
return list;
}
}