将JSON反序列化为通用,其中集合属性名称根据类型

时间:2016-08-05 13:29:59

标签: c# .net json json.net

我正在使用Newtonsoft.Json解析一些API响应,它们似乎有一个非常规则的结构,除了根据返回类型更改的数组属性名称。一些响应示例(包含假/空数据):

客户:

{
  "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",而在第二个中,它是"发票" (我们没有任何发票,因此我不确切知道该对象的结构是什么)。

我的最终目标是反序列化为类似这样的类结构:

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; }
}

由于这不能直接通过简单的DeserializeObject()调用(因为业务对象数组包含在该中间数&#34;数据&#34;属性)中,这似乎是&#39 ;更接近于所需的内容,但问题是[JsonProperty()]属性的移动目标:

public class Response {
    [JsonProperty("success")]
    public bool Success { get; set; }
    [JsonProperty("message")]
    public string Message { get; set; }
}

public class Response<T> : Response {
    public ResponseData Data { get; set; }
}

public class ResponseData<T> {
    [JsonProperty("total_count")]
    public int TotalCount { get; set; }

    [JsonProperty("???")] //Moving target
    public List<T> Data { get; set; }
}

将这种方法拉下来的最佳方法是什么?

3 个答案:

答案 0 :(得分:3)

JavaScriptDeserializer对象可以帮助您,dynamic

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,这对于大型计算来说非常缓慢(即,除非你有一些空闲的时间来杀死,否则不要迭代这些方法数千次!)

答案 1 :(得分:1)

当你要求&#34; sanest&#34;时,我不完全确定你的意思。解决这个问题的方法。 Json.NET支持[JsonExtensionData]将意外属性捕获到Dictionary<string, object>Dictionary<string, JToken>。但是,您的List<T> Data是类型化的,并且Json.NET没有任何内置功能可以将任意命名的属性反序列化为类型化对象。你还写了对转换为Dictionary 的中间步骤并不完全满意,所以听起来好像你想要一个避免反序列化为中间表示的解决方案。以下转换器可以实现此目的:

[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();
    }
}

然后将其添加到您的类型中,如下所示。 [JsonAnyPropertyName]属性指示应将未知属性反序列化的c#属性。

[JsonConverter(typeof(JsonAnyPropertyNameConverter))]
public class ResponseData<T>
{
    [JsonProperty("total_count")]
    public int TotalCount { get; set; }

    [JsonAnyPropertyName]
    public List<T> Data { get; set; }
}

public class Response
{
    [JsonProperty("success")]
    public bool Success { get; set; }
    [JsonProperty("message")]
    public string Message { get; set; }
}

public class Response<T> : Response
{
    [JsonProperty("data")]
    public ResponseData<T> Data { get; set; }
}

如果存在多个未知属性,则转换器会抛出异常而不是覆盖先前反序列化的数据。

答案 2 :(得分:0)

好吧,这是有效的,但它肯定不是我见过的最漂亮的东西(对于转换为字典的中间步骤并不完全满意)。暂时搁置这个问题,以防有人知道更好的方法。

各种对象:

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();
    }
}

然后获取回复:

public Response<Customer> GetCustomers() {
    string response = SendRequest("/api/v1/customers");
    Response<Customer> aresponse = JsonConvert.DeserializeObject<Response<Customer>>(response);
}