使用 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。
我在反序列化从 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。