C# 将JSON反序列化为泛型,其中集合属性名称根据类型更改
我正在使用Newtonsoft.Json解析一些API响应,它们似乎有一个非常规则的结构,除了根据返回类型更改的数组属性名。几个响应示例(假/空数据): 客户:C# 将JSON反序列化为泛型,其中集合属性名称根据类型更改,c#,.net,json,json.net,C#,.net,Json,Json.net,我正在使用Newtonsoft.Json解析一些API响应,它们似乎有一个非常规则的结构,除了根据返回类型更改的数组属性名。几个响应示例(假/空数据): 客户: { "success": true, "message": "Records Retrieved Successfully", "data": { "total_count": "1", "customer": [ { "id": "1234", "accountI
{
"success": true,
"message": "Records Retrieved Successfully",
"data": {
"total_count": "1",
"customer": [
{
"id": "1234",
"accountId": "220",
"email": "json.voorhees@lycos.com",
"name": "JSON Voorhees",
"company": "Test Company",
"customFieldsValues": [
{
"value": "Some Guy",
"field": {
"id": "69",
"name": "SalespersonID",
"label": "Account Manager"
}
}
]
}
]
}
}
{
"success": true,
"message": "Records Retrieved Successfully",
"data": {
"total_count": "0",
"invoice": []
}
}
发票:
{
"success": true,
"message": "Records Retrieved Successfully",
"data": {
"total_count": "1",
"customer": [
{
"id": "1234",
"accountId": "220",
"email": "json.voorhees@lycos.com",
"name": "JSON Voorhees",
"company": "Test Company",
"customFieldsValues": [
{
"value": "Some Guy",
"field": {
"id": "69",
"name": "SalespersonID",
"label": "Account Manager"
}
}
]
}
]
}
}
{
"success": true,
"message": "Records Retrieved Successfully",
"data": {
"total_count": "0",
"invoice": []
}
}
您会注意到,在第一个数组中,数组的属性名称是“customer”,在第二个数组中,它是“invoice”(我们没有任何invoice,所以我还不知道该对象的确切结构)
我的最终目标是反序列化到类结构中,如下所示:
public class Response {
[JsonProperty("success")]
public bool Success { get; set; }
[JsonProperty("message")]
public string Message { get; set; }
}
public class Response<T> : Response {
public List<T> Data { get; set; }
}
实现这一点最明智的方法是什么?JavaScriptDeserializer对象作为
动态对象在这里可能会帮助您:
var serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new[] { new DynamicJsonConverter() });
dynamic obj = serializer.Deserialize(json, typeof(object));
然后,获取所有属性以测试对象具有哪些属性:
var propertyInfo = obj.GetType().GetProperties();
然后,获取所需的属性名称并将其传入:
var value = obj.data[0].GetType().GetProperty(propertyName).GetValue(obj, null);
作为一个循环示例:
foreach (var property in obj.GetType().GetProperties()) {
Console.WriteLine(String.Format("The value for property {0} is {1}.",
property.Name,
obj.data[0].GetType().GetProperty(propertyName).GetValue(obj, null));
}
注意:这个答案使用的是System.Reflection
,这对于大型计算来说非常缓慢(也就是说,除非你有空闲时间消磨时间,否则不要迭代这些方法数千次!)好吧,这是可行的,但它肯定不是我见过的最漂亮的东西(对转换成字典的中间步骤不太满意)。暂时把这个问题留着,以防有人知道更好的方法
各种对象:
public class Response {
[JsonProperty("success")]
public bool Success { get; set; }
[JsonProperty("message")]
public string Message { get; set; }
}
public class Response<T> : Response {
[JsonProperty("data")]
[JsonConverter(typeof(DataConverter))]
public List<T> Data { get; set; }
}
public class DataConverter : JsonConverter {
public override bool CanConvert(Type objectType) {
return typeof(List<object>).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
Dictionary<string, object> data = (Dictionary<string, object>)serializer.Deserialize(reader, typeof(Dictionary<string, object>));
foreach (KeyValuePair<string, object> kvp in data) {
if (kvp.Key != "total_count") {
return ((JToken)kvp.Value).ToObject(objectType);
}
}
return null;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
throw new NotImplementedException();
}
}
公共类响应{
[JsonProperty(“成功”)]
公共bool成功{get;set;}
[JsonProperty(“消息”)]
公共字符串消息{get;set;}
}
公共课回应:回应{
[JsonProperty(“数据”)]
[JsonConverter(类型(数据转换器))]
公共列表数据{get;set;}
}
公共类数据转换器:JsonConverter{
公共覆盖布尔CanConvert(类型objectType){
返回typeof(List).IsAssignableFrom(objectType);
}
公共重写对象ReadJson(JsonReader阅读器,类型objectType,对象existingValue,JsonSerializer序列化程序){
字典数据=(字典)序列化程序。反序列化(读取器,typeof(字典));
foreach(数据中的KeyValuePair kvp){
如果(kvp.Key!=“总计数”){
返回((JToken)kvp.Value).ToObject(objectType);
}
}
返回null;
}
公共重写void WriteJson(JsonWriter编写器、对象值、JsonSerializer序列化器){
抛出新的NotImplementedException();
}
}
然后获取响应:
public Response<Customer> GetCustomers() {
string response = SendRequest("/api/v1/customers");
Response<Customer> aresponse = JsonConvert.DeserializeObject<Response<Customer>>(response);
}
公共响应GetCustomers(){
字符串响应=SendRequest(“/api/v1/customers”);
Response aresponse=JsonConvert.DeserializeObject(响应);
}
我不太清楚你问“最理智”是什么意思解决此问题的方法。Json.NET支持将意外属性捕获到字典
或字典
中。但是,您的列表数据
是类型化的,Json.NET没有内置将任意命名的属性反序列化为类型化对象的功能。您还编写了一个不完全满意的中间step转换为字典的方法,因此听起来好像您想要一个避免反序列化为中间表示的解决方案。以下转换器可实现此目的:
[System.AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class JsonAnyPropertyNameAttribute : System.Attribute
{
}
class JsonAnyPropertyNameConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException("This converter is intended to be applied directly to a type or a property.");
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
try
{
int defaultCount = 0;
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(objectType);
if (existingValue == null)
existingValue = contract.DefaultCreator();
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonToken.Comment:
break;
case JsonToken.PropertyName:
{
var name = reader.Value.ToString();
var property = contract.Properties.GetClosestMatchProperty(name);
if (!reader.Read())
throw new JsonSerializationException(string.Format("Missing value at path: {0}", reader.Path));
if (property == null)
{
property = contract.Properties.Where(p => p.AttributeProvider.GetAttributes(true).OfType<JsonAnyPropertyNameAttribute>().Any()).Single();
defaultCount++;
if (defaultCount > 1)
{
throw new JsonSerializationException(string.Format("Too many properties with unknown names for type {0} at path {1}", objectType, reader.Path));
}
}
var value = serializer.Deserialize(reader, property.PropertyType);
property.ValueProvider.SetValue(existingValue, value);
}
break;
case JsonToken.EndObject:
return existingValue;
default:
throw new JsonSerializationException(string.Format("Unknown token {0} at path: {1} ", reader.TokenType, reader.Path));
}
}
throw new JsonSerializationException(string.Format("Unclosed object at path: {0}", reader.Path));
}
catch (Exception ex)
{
if (ex is JsonException)
throw;
// Wrap any exceptions encountered in a JsonSerializationException
throw new JsonSerializationException(string.Format("Error deserializing type {0} at path {1}", objectType, reader.Path), ex);
}
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
如果有多个未知属性,转换器将抛出异常,而不是覆盖以前反序列化的数据。谢谢,看起来它可能会起作用,不过如果可能的话,我希望避免反射,因为我不知道数据集最终会有多大(在本项目中,或将来的项目中).可能是这样,但是浏览JsonObject
而不是反射类型?你说的“sanest”是什么意思?你的转换器很简洁。你是在寻找一种避免加载中间表示的解决方案吗?@dbc只是想知道是否有更好的方法来实现它,而不需要先反序列化到字典,然后从字典中挑选。如果这是最简单的方法,我可以接受。