使用 Newtonsoft Json.NET 解析具有名称值对的多维 JSON 数组

Parsing Multidimensional JSON Array with Name Value Pairs using Newtonsoft Json.NET

我在反序列化从 javascript 发送到我的 ASP.NET 4.52 C# WEB API 以存储到数据库中的 JSON 字符串时遇到困难。 JSON 字符串源自我正在编写的战舰游戏的多维 JavaScript 数组。 数据表示 x/y 坐标网格上船舶的单元格位置。 在我的 array/string 中,我有 5 艘船,对于每艘船,我有不同数量的 x/y 位置(以名称-值对的形式示例:第 3 行;第 3 列) 下面的字符串是我的 C# WebAPI 接收到的数据,表示通过 ajax HTTPPOST

发送的多维 JavaScript 数组数据

ShipAndPositions(我的 json 字符串):

var ShipAndPositions = "[\"ship5\",[{\"row\":4,\"col\":6},{\"row\":5,\"col\":6},{\"row\":6,\"col\":6}],\"ship4\",[{\"row\":3,\"col\":8},{\"row\":3,\"col\":9},{\"row\":3,\"col\":10},{\"row\":3,\"col\":11}],\"ship3\",[{\"row\":8,\"col\":2},{\"row\":8,\"col\":3},{\"row\":8,\"col\":4},{\"row\":8,\"col\":5}],\"ship2\",[{\"row\":9,\"col\":7},{\"row\":9,\"col\":8},{\"row\":9,\"col\":9},{\"row\":9,\"col\":10},{\"row\":9,\"col\":11}],\"ship1\",[{\"row\":0,\"col\":0},{\"row\":0,\"col\":1},{\"row\":0,\"col\":2},{\"row\":0,\"col\":3},{\"row\":0,\"col\":4},{\"row\":0,\"col\":5}],\"ship0\",[{\"row\":12,\"col\":5},{\"row\":12,\"col\":6},{\"row\":12,\"col\":7},{\"row\":12,\"col\":8},{\"row\":12,\"col\":9},{\"row\":13,\"col\":5},{\"row\":13,\"col\":6},{\"row\":13,\"col\":7},{\"row\":13,\"col\":8},{\"row\":13,\"col\":9}]]"

根据 jsonlint.com

,此字符串有效 JSON

我将我的 class 定义为:

public class CoordPoints
{
    public string row { get; set; }

    public string col { get; set; }
}

public class CoordPointsArray
{
    public string ship { get; set; }

    //public List<CoordPoints> ShipPositions { get; set; }
    public Dictionary<string, CoordPoints[]> CoordPoints { get; private set; }
}

我安装了 newtonsoft.json.dll 4.5.8 我已经尝试了以下所有方法,它们都抛出异常

1) DataContractJsonSerializer jsonObjectCoordsInfo = new DataContractJsonSerializer(typeof(CoordPointsArray)); MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(data.ShipAndPositions)); CoordPointsArray test1 (CoordPointsArray)jsonObjectCoordsInfo.ReadObject(流);

2) var shipPositionList = serializer.Deserialize(data.ShipAndPositions);

3) var test3 = JsonConvert.DeserializeObject(data.ShipAndPositions);

4) CoordPointsArray rootObject = new CoordPointsArray(); rootObject = JsonConvert.DeserializeObject(data.ShipAndPositions);

我尝试过许多其他变体。谁能帮我解决这个问题。

这里不是多维数组。相反,您拥有的是对象集合的属性集合 呈现为平面列表 。如果我为了便于阅读而格式化你的 JSON 字符串,它看起来像:

[
  "ship5",
  [{"row":4,"col":6},{"row":5,"col":6},{"row":6,"col":6}],
  "ship4",
  [{"row":3,"col":8},{"row":3,"col":9},{"row":3,"col":10},{"row":3,"col":11}],
  "ship3",
  [{"row":8,"col":2},{"row":8,"col":3},{"row":8,"col":4},{"row":8,"col":5}],
  "ship2",
  [{"row":9,"col":7},{"row":9,"col":8},{"row":9,"col":9},{"row":9,"col":10},{"row":9,"col":11}],
  "ship1",
  [{"row":0,"col":0},{"row":0,"col":1},{"row":0,"col":2},{"row":0,"col":3},{"row":0,"col":4},{"row":0,"col":5}],
  "ship0",
  [{"row":12,"col":5},{"row":12,"col":6},{"row":12,"col":7},{"row":12,"col":8},{"row":12,"col":9},{"row":13,"col":5},{"row":13,"col":6},{"row":13,"col":7},{"row":13,"col":8},{"row":13,"col":9}]
]

通过适当的格式化,我们现在可以看到它符合以下模式:

[
    Object1Property1Value,
    Object1Property2Value,

    Object2Property1Value,
    Object2Property2Value,

    Object3Property1Value,
    Object3Property2Value,
]

您想要做的是将此平面属性列表反序列化为 List<T> 某种类型 T,其属性对应于平面列表单个部分的值以上。

首先,我想说这不是表示数据的特别好方法。 JSON 的结构应该反映数据的结构,但在本例中并非如此。更好的格式是 JSON 对象的列表,或者在最坏的情况下是每个对象的值数组的数组(在这种情况下,您可以使用 Json.NET 反序列化您的 JSON,利用 中的 ObjectToArrayConverter<CoordPointsArray>。)

话虽这么说,如果你不能改变 JSON 格式,这个 JSON 可以通过 custom JsonConverter 使用 Json.NET 成功反序列化。首先,按如下方式定义您的数据模型:

public class CoordPoint
{
    public int row { get; set; }
    public int col { get; set; }
}

public class CoordPointsArray
{
    [JsonProperty(Order = 1)]
    public string Name { get; set; }

    [JsonProperty(Order = 2)]
    public List<CoordPoint> Coordinates { get; set; }
}

注意到 [JsonProperty(Order = X)] 属性了吗?这些将向自定义转换器指示 属性 值将出现在 JSON 中的顺序。

接下来,定义以下转换器:

public class ObjectListToSequentialPropertyArrayConverter<T> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(T) == objectType.GetListType();
    }

    static bool ShouldSkip(JsonProperty property)
    {
        return property.Ignored || !property.Readable || !property.Writable;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var objectType = value.GetType();
        var itemType = objectType.GetListType();
        if (itemType == null)
            throw new ArgumentException(objectType.ToString());
        var itemContract = serializer.ContractResolver.ResolveContract(itemType) as JsonObjectContract;
        if (itemContract == null)
            throw new JsonSerializationException("invalid type " + objectType.FullName);
        var list = (IList)value;
        var propertyList = list
            .OfType<Object>()
            .Where(i => i != null) // Or should we throw an exception?
            .SelectMany(i => itemContract.Properties.Where(p => !ShouldSkip(p)).Select(p => p.ValueProvider.GetValue(i)));
        serializer.Serialize(writer, propertyList);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var itemType = objectType.GetListType();
        if (itemType == null)
            throw new ArgumentException(objectType.ToString());
        if (reader.TokenType == JsonToken.Null)
            return null;
        var array = JArray.Load(reader);
        var listContract = serializer.ContractResolver.ResolveContract(objectType) as JsonArrayContract;
        var itemContract = serializer.ContractResolver.ResolveContract(itemType) as JsonObjectContract;
        if (itemContract == null || listContract == null)
            throw new JsonSerializationException("invalid type " + objectType.FullName);
        var list = existingValue as IList ?? (IList)listContract.DefaultCreator();
        var properties = itemContract.Properties.Where(p => !ShouldSkip(p)).ToArray();

        for (int startIndex = 0; startIndex < array.Count; startIndex += properties.Length)
        {
            var item = itemContract.DefaultCreator();
            for (int iProperty = 0; iProperty < properties.Length; iProperty++)
            {
                if (startIndex + iProperty >= array.Count)
                    break;
                var propertyValue = array[startIndex + iProperty].ToObject(properties[iProperty].PropertyType, serializer);
                properties[iProperty].ValueProvider.SetValue(item, propertyValue);
            }
            list.Add(item);
        }
        return list;
    }
}

public static class TypeExtensions
{
    public static Type GetListType(this Type type)
    {
        while (type != null)
        {
            if (type.IsGenericType)
            {
                var genType = type.GetGenericTypeDefinition();
                if (genType == typeof(List<>))
                    return type.GetGenericArguments()[0];
            }
            type = type.BaseType;
        }
        return null;
    }
}

最后,反序列化你的 JSON 如下:

var settings = new JsonSerializerSettings
{
    Converters = { new ObjectListToSequentialPropertyArrayConverter<CoordPointsArray>() },
};

var root = JsonConvert.DeserializeObject<List<CoordPointsArray>>(json, settings);

工作 .Net fiddle 显示对此格式的反序列化和重新序列化。

请注意,我已经使用 Json.NET 版本 10(当前版本)和版本 6(在 https://dotnetfiddle.net/ 上)进行了测试,但没有使用 4.5.8。