首页 > 解决方案 > 无法确定 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。

我如何在这里构造数据对象?

标签: c#jsonjson.net

解决方案


我不得不说,这是我在野外见过的比较不友好的 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

工作演示:https ://dotnetfiddle.net/Slkufm


推荐阅读