1

概述:
我正在尝试制作一个应用程序,将本地 JSON 反序列化为列表,然后将其转换为 DataTable,然后将其显示到 DataGridView 中。

我正在使用的数据是用于交易卡牌游戏 JSON 包含卡牌列表,每个卡牌对象都有一堆属性、语言、名称等……
下面将详细介绍数据源。

问题:
我有一个半工作项目,但我无法弄清楚为什么一些属性和数据值在反序列化时没有传递到我的列表中,因为对象没有 RootObjectLevel

每次使用比根级别更深的 { } 封装 abject 时,例如  legalities在下面的代码中,我的 DataGridView 只显示一列带有 (Collection) 或namespace.classname而不是所有属性列。

用于描述上述内容的示例压缩 JSON 代码(之后提供完整的 JSON)

{
  "lang": "en",
  "set": "lea",
  "set_name": "Limited Edition Alpha",
  "collector_number": "142",
  "name": "Dwarven Demolition Team",
  "legalities": {
    "standard": "not_legal",
    "future": "not_legal",
    "historic": "not_legal",
    "gladiator": "not_legal",
    "pioneer": "not_legal",
    "modern": "legal",
    "legacy": "legal",
    "pauper": "not_legal",
    "vintage": "legal",
    "penny": "not_legal",
    "commander": "legal",
    "brawl": "not_legal",
    "duel": "legal",
    "oldschool": "legal",
    "premodern": "not_legal"
  },
}

为卡片返回的完整 JSON 示例,因此您可以查看整个内容:
https ://api.scryfall.com/cards/03482c9c-1f25-4d73-9243-17462ea37ac4

您可以选择查看原始数据,甚至可以对其进行格式化,使其像上面那样易于阅读。

JSON 数据源 – https://scryfall.com/docs/api/bulk-data

我正在使用 « Default Cards » 和 « All Cards » 文件,第二个是 1,2Go

目标:
我想要实现的正常行为或结果是将“合法性”中的所有 15 个不同对象显示为标题:{}

| 标准 | 未来 | 历史 | ETC...

以及每行每张卡片的legalor值。not legal

而不是得到这个结果: 运行我的代码的当前结果屏幕截图

我的猜测:
我没有任何错误,它适用于所有数据,RootObject但数据比RootObjectLevel仅仅返回 null 或没有以某种方式正确填充列表。

我怀疑我需要一个自定义转换器,一个令牌阅读器,也许使用字典或类似的东西,但在寻找一周后我只是一无所知,我对 C# 太陌生了,我猜,它已经像 2 周以来我开始编码。

我什至不确定我是否能够记住到目前为止我感到疲倦的所有事情,并在此处列出它们,但是当我尝试再次进行新搜索时,并没有那么多链接显示为“未读”解决我的问题。

到目前为止,我已经能够在 Google 上找到我需要的东西,但这次我需要一些帮助。

现在代码:
pictureBox_Click打开我的本地 JSON 并将其路径写入Label.Text

private void pictureBoxScryfallOpenFile_Click(object sender, EventArgs e)
{
    using (var openFileDialog = new OpenFileDialog())
    {
        openFileDialog.Title = "Please select a JSON file";
        openFileDialog.Filter = "JSON files (*.json) | *.json";
        openFileDialog.RestoreDirectory = true;

        if (openFileDialog.ShowDialog() == DialogResult.OK)
        {
            string fileName = openFileDialog.FileName;

            labelScryfallOpenedFile.Visible = true;
            labelScryfallOpenedFile.Text = fileName;
        }
    }
}

然后我从新的路径中获取路径以Label.Text开始反序列化。label.Text.ToString()buttom_Click

除此之外,我还有一个带有 4 个选项的 ComboBox,每个选项都在If调用触发具有我想从文件中读取的 JSONProperty 的好类

然后我将我的对象列表反序列化为一个列表,该列表被转换为一个设置为 DataGridView 的数据源的 DataTable。

我正在缩小没有 ComboBox 和重现问题的最少代码的代码。

using Newtonsoft.Json;

public void buttonReadScryfall_Click(object sender, EventArgs e)
{
    if (labelScryfallOpenedFile.Visible == true)
    {
        string jsonFilePath = labelScryfallOpenedFile.Text.ToString();

        using (var jsonFile = new StreamReader(jsonFilePath))
        {                   
            var jsonTextReader = new JsonTextReader(jsonFile);

            var serializer = new JsonSerializer();
            
            var cardLegalities = serializer.Deserialize<List<CardLegalities>>(jsonTextReader);

            DataTable dtLegalities = ConvertToDataTable(cardLegalities);

            dataGridView.DataSource = null;
            dataGridView.DataSource = dtLegalities;
            dataGridView.Refresh();                  
        }
    }
    else
    {
        MessageBox.Show("!!! No File Selected !!!"
                        + Environment.NewLine +
                        "Open a Local File Using the Appropriate Folder Icon"
                        + Environment.NewLine +
                        "Before Attempting to Deserialize a JSON");
    }
}

注意:
我需要 StreamReader,在这种情况下它甚至是强制性的,如果我使用标准reader.ReadAllText(),它最终会出现内存不足异常,因为我正在使用的最大文件是 1.2Go,我们正在谈论超过 300k 行如果我决定使用可用于每张卡的整个 JSON 属性,则有一百列。

我还需要转换为 DataTable,否则我无法使用DefaultView.RowFilter.

转换为数据表

public static DataTable ConvertToDataTable<T>(IList<T> data)
{
    PropertyDescriptorCollection properties =
        TypeDescriptor.GetProperties(typeof(T));
    
    DataTable table = new DataTable();

    foreach (PropertyDescriptor prop in properties)
        table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
    foreach (T item in data)
    {
        DataRow row = table.NewRow();
        foreach (PropertyDescriptor prop in properties)
            row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
        table.Rows.Add(row);
    }
    return table;
}

最后是随之而来的类:

using Newtonsoft.Json;

public class CardLegalities
{
    [JsonProperty("lang")]
    public string Lang { get; set; }

    [JsonProperty("set")]
    public string Set { get; set; }

    [JsonProperty("set_name")]
    public string SetName { get; set; }

    [JsonProperty("collector_number")]
    public string CollectorNumber { get; set; }

    [JsonProperty("name")]
    public string Name { get; set; }

    /// <summary>
    /// An object describing the legality of this card in different formats
    /// </summary>
    [JsonProperty("legalities")]
    public Legalities Legalities { get; set; }
}

public class Legalities
{
    [JsonProperty("standard")]
    public string Standard { get; set; }

    [JsonProperty("future")]
    public string Future { get; set; }

    [JsonProperty("historic")]
    public string Historic { get; set; }

    [JsonProperty("gladiator")]
    public string Gladiator { get; set; }

    [JsonProperty("pioneer")]
    public string Pioneer { get; set; }

    [JsonProperty("modern")]
    public string Modern { get; set; }

    [JsonProperty("legacy")]
    public string Legacy { get; set; }

    [JsonProperty("pauper")]
    public string Pauper { get; set; }

    [JsonProperty("vintage")]
    public string Vintage { get; set; }

    [JsonProperty("penny")]
    public string Penny { get; set; }

    [JsonProperty("commander")]
    public string Commander { get; set; }

    [JsonProperty("brawl")]
    public string Brawl { get; set; }

    [JsonProperty("duel")]
    public string Duel { get; set; }

    [JsonProperty("oldschool")]
    public string Oldschool { get; set; }

    [JsonProperty("premodern")]
    public string Premodern { get; set; }
}

我真的很想知道如何解决这个问题,我很确定我会看到一些我在 Google 搜索中看到但无法适应我的需求的东西,因为我对 C# 编码太陌生了。
如果这样更方便的话,我最终可以将应用程序推送到 GitHub 以便您克隆存储库。

4

2 回答 2

1

要回答您的主要问题,您可以使用此答案JsonPathConverter第二部分中的描述来允许您将所有属性移动到您的类中以使其更易于处理。您只需将属性添加到类中,然后为嵌套属性的所有名称添加前缀,例如.LegalitiesCardLegalities[JsonConverter(typeof(JsonPathConverter))]CardLegalitiesJsonPropertylegalities.[JsonProperty("legalities.standard")]

这将使您大部分时间到达那里。但是,您在评论中提到您的某些属性是数组,例如colors,并且您没有看到DataGridView.

这里的问题是DataGridView控件在绑定到DataTable. 您可以使用以下代码轻松地对此进行测试:

DataTable dt = new DataTable();
dt.Columns.Add("Simple String", typeof(string));
dt.Columns.Add("Simple Int", typeof(int));
dt.Columns.Add("Array of String", typeof(string[]));
dt.Rows.Add("Foo", 25, new string[] { "A", "B" });

dataGridView1.DataSource = null;
dataGridView1.DataSource = dt;
dataGridView1.Refresh();

Form如果您在包含 a的新文件中运行上述代码DataGridView,您将看到以下输出,其中“Array of String”列明显缺失:

测试代码截图

这意味着在进行数据表转换时,您需要将数组属性转换为简单字符串(通过连接值)。这是您的ConvertToDataTable<T>()方法的更新版本,应该可以解决问题:

public static DataTable ConvertToDataTable<T>(IList<T> data)
{
    PropertyDescriptorCollection properties =
        TypeDescriptor.GetProperties(typeof(T));

    DataTable table = new DataTable();

    foreach (PropertyDescriptor prop in properties)
    {
        Type dataType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType;
        if (dataType.IsArray) dataType = typeof(string);
        table.Columns.Add(prop.Name, dataType);
    }

    foreach (T item in data)
    {
        DataRow row = table.NewRow();
        foreach (PropertyDescriptor prop in properties)
        {
            object val = prop.GetValue(item);
            if (val != null && prop.PropertyType.IsArray)
            {
                var items = ((IEnumerable)val)
                    .Cast<object>()
                    .Where(o => o != null)
                    .Select(o => o.ToString());

                val = string.Join(", ", items);
            }
            row[prop.Name] = val ?? DBNull.Value;
        }
        table.Rows.Add(row);
    }
    return table;
}
于 2021-07-17T05:20:06.713 回答
0

你也可以试试Cinchoo ETL,一个用于这种类型转换的开源库

这是示例代码

using ChoETL;

ChoETLSettings.NestedKeySeparator = '.';
using (var r = new ChoJSONReader<CardLegalities>("*** YOUR JSON FILE PATH ***")
    )
{
    var dt = r.AsDataTable();
    Console.WriteLine(dt.Dump());
}

免责声明:我是这个库的作者

于 2021-07-18T02:59:57.500 回答