【问题标题】:Unable to determine data structure for JSON data无法确定 JSON 数据的数据结构
【发布时间】:2020-07-04 03:38:02
【问题描述】:

我有以下 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。

这里如何构造数据对象?

【问题讨论】:

  • 首先field_type都错了,应该是"field_type": "uint8"、"string"等等。
  • 总是验证您的 JSON 输入作为调试中的第一步。你的无效。
  • 不要依赖代码生成器。虽然这样的工具可以派上用场,但除非您了解如何手写输出,否则绝对不应该使用。
  • @FranzGleichmann 我已经验证了文件,但我做了一些手动编辑,所以我不必发布 100k 行文件,在那里犯了一个小错误。更新问题并感谢您的指出。
  • 使用更新的json 尝试投射,就像Root myDeserializedClass = JsonConvert.DeserializeObject&lt;Root&gt;(myJsonResponse); 我认为,这个Root myDeserializedClass = JsonConvert.DeserializeObject(myJsonResponse); 会给你构建错误。或将Root 更改为动态或对象。

标签: c# json json.net


【解决方案1】:

我不得不说,这是我在野外见过的比较不友好的 JSON 格式之一。主要问题是一些数组包含混合类型,这使得声明类来表示它们变得困难。 JSON 类生成器通常不知道在这种情况下该做什么,因此它们只是默认使用List&lt;object&gt;,正如您所见。很多时候,您需要自己提出一个合理的类模型,然后使用自定义的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 数组元素是对象,每个对象都只包含一个键,但每个键不同。我不知道这个键是代表 name 还是 type 还是什么,所以我只是将其命名为 Key 并将其滚动到 VersionedFile 类中。李>
  • 版本化文件有两种不同的格式,我将其称为“DB”和“Non-DB”格式。 “非 DB”格式是一个对象数组。 “DB”格式是一个包含两个元素的数组:一个字符串后跟一个与“Non-DB”格式具有相同形状的对象数组。我假设“DB”数组永远不会包含任何附加元素,并且该字符串只是与Items 列表相关的附加标签或标题。我没有为这两种格式创建单独的类,而是决定重用相同的VersionedFile 类并添加一个额外的Label 属性来捕获字符串。对于“非 DB”格式,Label 将始终为空。

下面是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

【讨论】:

  • 如果数组是异构的,你可以考虑将它声明为dynamic[]。您也可以使用Newtonsoft.Json.Linq.Object[]
  • 效果接近完美,但在运行它时,我注意到我的数组中还有另外两个对象。它们不包含“表名”属性,但除此之外它们是相同的。有办法处理吗?我已经更新了原始问题以反映新对象
  • 是的,如果我们放弃 ArrayToObjectConverter 并改用自定义 JsonConverter,我们可以处理这两种格式。这也将使我们能够简化模型并摆脱Dictionary。我已经更新了我的答案。
  • 干得好,非常有帮助!学到了很多关于自定义json解析的知识!谢谢
  • 没问题;很高兴我能帮上忙!
猜你喜欢
  • 2019-11-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多