解决使用单独的列和行解析JSON响应的问题

时间:2017-04-06 06:00:53

标签: c# json json.net

我遇到了解决如何解析我从第三方回来的JSON响应的问题。我需要将以下JSON(我正在获得的简化示例)转换为C#类。主要问题是列可能采用不同的顺序,有额外的字段或缺少的字段(最后应该最终为空值)。

{
    "columns": [
        { "id": { "type": "Numeric", "nullable": false } },
        { "name": { "type": "Text", "nullable": false } },
        { "description": { "type": "Text", "nullable": true } },
        { "last_updated": { "type": "DateTime", "nullable": false } }
    ],
    "rows": [
        [1, "foo", "Lorem ipsum", "2016-10-26T00:09:14Z"],
        [4, "bar", null, "2013-07-01T13:04:24Z"]
    ]
}

此示例的C#类将是

public class Record
{
    public int id { get; set; }
    public string name { get; set; }
    public string description { get; set; }
    public DateTime? last_updated { get; set; }
}

我尝试过使用自定义json转换器,但是没有太多运气来使用它来处理分离的元数据和值。有没有人对如何解析这类数据有任何想法?最终会有多个"记录"类型,这就是服务器的响应可以是动态的。

2 个答案:

答案 0 :(得分:1)

您的问题类似于this recent one,可以使用类似的转换器解决:

public class RowColumnListConverter<T> : JsonConverter
{
    const string columnsKey = "columns";
    const string rowsKey = "rows";

    public override bool CanConvert(Type objectType)
    {
        if (!typeof(ICollection<T>).IsAssignableFrom(objectType))
            return false;
        // This converter is only implemented for read/write collections.  So no arrays.
        if (objectType.IsArray)
            return false;
        return true;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var list = existingValue as ICollection<T> ?? (ICollection<T>)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
        var root = JObject.Load(reader);
        var rows = root.GetValue(rowsKey, StringComparison.OrdinalIgnoreCase).NullCast<JArray>();
        if (rows == null)
            return list;
        var columns = root.GetValue(columnsKey, StringComparison.OrdinalIgnoreCase).NullCast<JArray>();
        if (columns == null)
            throw new JsonSerializationException(columnsKey + " not found.");
        var columnNames = columns.Cast<JObject>().SelectMany(o => o.Properties()).Select(p => p.Name).ToArray();

        foreach (var row in rows)
        {
            if (row == null || row.Type == JTokenType.Null)
                list.Add(default(T));
            else if (row.Type == JTokenType.Array)
            {
                var o = new JObject(columnNames.Zip(row, (c, r) => new JProperty(c, r)));
                list.Add(o.ToObject<T>(serializer));
            }
            else
                throw new JsonSerializationException(string.Format("Row was not an array: {0}", row));
        }

        return list;
    }

    public override bool CanWrite { get { return false; } }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

public static class JsonExtensions
{
    public static TJTOken NullCast<TJTOken>(this JToken token) where TJTOken : JToken
    {
        if (token == null || token.Type == JTokenType.Null)
            return null;
        var typedToken = token as TJTOken;
        if (typedToken == null)
            throw new JsonSerializationException(string.Format("Token {0} was not of type {1}", token.ToString(Formatting.None), typeof(TJTOken)));
        return typedToken;
    }
}

然后使用它:

var list = JsonConvert.DeserializeObject<List<Record>>(json, new RowColumnListConverter<Record>());

或者

var list = JsonConvert.DeserializeObject<List<Record>>(json, new JsonSerializerSettings
                                                       {
                                                           Converters = { new RowColumnListConverter<Record>() },
                                                       });

转换器的工作原理是将外部对象加载到临时JObject中,然后将"columns""rows"数组重新格式化为更常规的对象列表以进行反序列化。请注意,没有尝试使用"columns"列表中的类型信息,只是假设POCO成员具有正确的类型。此外,WriteJson()未实现,因为问题中没有足够的信息来确定如何为任何可能的列类型发出类型信息;需要完整的规范。

示例fiddle

答案 1 :(得分:0)

我认为这种方法在某种程度上是棘手的,但是如果你必须遵循这一点,你可以为它们下订单属性来解决你的订单问题:

    {
    "columns": [
        { "id": { "type": "Numeric", "nullable": false, "order":1 } },
        { "name": { "type": "Text", "nullable": false, "order":2 } },
        { "description": { "type": "Text", "nullable": true, "order":3 } },
        { "last_updated": { "type": "DateTime", "nullable": false, "order":4 } }
    ],
    "rows": [
        [1, "foo", "Lorem ipsum", "2016-10-26T00:09:14Z"],
        [4, "bar", null, "2013-07-01T13:04:24Z"]
    ]
}

对于null值,您可以获取一个默认值来识别它们,并在自定义json转换器中替换为null。

您的数据的最佳结构可以是:

public class Column
{
    public string type { get; set; }
    public bool nullable { get; set; }
    public int order { get; set; }
}

public class Model
{
    public List<Dictionary<string, Column>> columns { get; set; }
    public List<List<string>> rows { get; set; }
}

您可以直接将您的json转换为Model类。