c# - 无法确定 JSON 数据的数据结构
问题描述
我有以下 JSON 文件,我在解析时遇到问题。
{
"version": 2,
"versioned_files": [
{
"DB": [
"Table0",
[
{
"version": 0,
"fields": [
{
"name": "key",
"type": "StringU8"
},
{
"name": "value",
"type": "Float"
}
],
"localised": []
}
]
]
},
{
"NoDbObject": [
{
"version": 1,
"fields": [
{
"name": "objectProp",
"type": "StringU8"
}
],
"localised": []
}
]
}
]
}
https://json2csharp.com/为我生成以下代码,这不是很有帮助:
// Root myDeserializedClass = JsonConvert.DeserializeObject(myJsonResponse);
public class Root
{
public int version { get; set; }
public List<List<object>> files { get; set; }
}
我尝试过的大部分内容都给了我一个空文件列表或以下错误:
Newtonsoft.Json.JsonSerializationException:
无法将当前 JSON 数组(例如 [1,2,3])反序列化为类型“RonParser.VersionedFile”,因为该类型需要 JSON 对象(例如 {"name":"value"})来反序列化正确。要修复此错误,请将 JSON 更改为 JSON 对象(例如 {"name":"value"})或将反序列化类型更改为数组或实现集合接口的类型(例如 ICollection、IList),例如可以从 JSON 数组反序列化。JsonArrayAttribute 也可以添加到类型中以强制它从 JSON 数组反序列化。路径“文件 [0]”,第 4 行,位置 5。
我如何在这里构造数据对象?
解决方案
我不得不说,这是我在野外见过的比较不友好的 JSON 格式之一。主要问题是一些数组包含混合类型,这使得声明类来表示它们变得困难。JSON 类生成器通常不知道在这种情况下该做什么,所以它们只是默认使用 using List<object>
,正如您所见。很多时候,您需要自己提出一个合理的类模型,然后使用自定义JsonConverter
来填充它。
这是我为您的 JSON 提出的模型:
class RootObject
{
public int Version { get; set; }
[JsonProperty("versioned_files")]
public List<VersionedFile> VersionedFiles { get; set; }
}
[JsonConverter(typeof(VersionedFileConverter))]
class VersionedFile
{
public string Key { get; set; }
public string Label { get; set; }
public List<Item> Items { get; set; }
}
class Item
{
public int Version { get; set; }
public List<Field> Fields { get; set; }
public List<object> Localised { get; set; }
}
class Field
{
public string Name { get; set; }
public string Type { get; set; }
}
关于模型的一些说明:
- 在 JSON 中,
versioned_files
数组元素是对象,每个对象都只包含一个键,但每个键都不相同。我不知道这个键是代表名称还是类型还是什么,所以我只是调用它Key
并将其滚动到VersionedFile
类中。 - 版本化文件有两种不同的格式,我将其称为“DB”和“Non-DB”格式。“非 DB”格式是一个对象数组。“DB”格式是一个包含两个元素的数组:一个字符串后跟一个与“Non-DB”格式具有相同形状的对象数组。我假设“DB”数组永远不会包含任何其他元素,并且该字符串只是与
Items
. 我没有为这两种格式创建单独的类,而是决定重用同一个VersionedFile
类并添加一个额外的Label
属性来捕获字符串。Label
对于“非 DB”格式,将始终为空。
下面是VersionedFileConverter
. 转换器使用LINQ-to-JSON API来确定每个VersionedFile
对象使用哪种格式,然后相应地填充它。它还处理动态键。
public class VersionedFileConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(VersionedFile);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
JProperty prop = obj.Properties().First();
VersionedFile file = new VersionedFile
{
Key = prop.Name,
Items = new List<Item>()
};
JArray array = (JArray)prop.Value;
if (array.Count > 0)
{
if (array[0].Type == JTokenType.String)
{
file.Label = (string)array[0];
file.Items = array[1].ToObject<List<Item>>(serializer);
}
else
{
file.Items = array.ToObject<List<Item>>(serializer);
}
}
return file;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
有了这一切,我们可以像这样反序列化和转储数据:
var root = JsonConvert.DeserializeObject<RootObject>(json);
Console.WriteLine("Root version: " + root.Version);
Console.WriteLine("Versioned files:");
foreach (var vf in root.VersionedFiles)
{
Console.WriteLine(" Key: " + vf.Key);
Console.WriteLine(" Label: " + (vf.Label ?? "(none)"));
Console.WriteLine(" Items:");
foreach (var item in vf.Items)
{
Console.WriteLine(" Version: " + item.Version);
Console.WriteLine(" Fields:");
foreach (var field in item.Fields)
{
Console.WriteLine(" Field name: " + field.Name);
Console.WriteLine(" Field type: " + field.Type);
Console.WriteLine();
}
}
}
这给出了以下输出:
Root version: 2
Versioned files:
Key: DB
Label: Table0
Items:
Version: 0
Fields:
Field name: key
Field type: StringU8
Field name: value
Field type: Float
Key: NoDbObject
Label: (none)
Items:
Version: 1
Fields:
Field name: objectProp
Field type: StringU8