如何在空DataTable的JSON中包含列元数据

本文关键字:JSON 包含列 元数据 DataTable | 更新日期: 2023-09-27 18:20:26

我希望在JSON中正确描述列元数据,稍后Newtonsoft将对其进行解析以构建C#DataTable。通过这种方式,我希望解决一个问题,即获得一个没有行或列的DataTable,但我需要用标签创建列,希望用数据类型创建列,即使我传递了一个空表。

标准输入示例:

{
    "BrokerID": "998",
    "AccountID": "1313",
    "Packages": [
        {
            "PackageID": 226,
            "Amount": 15000,
            "Auto_sync": true,
            "Color": "BLUE"
        },
        {
            "PackageID": 500,
            "Amount": 15000,
            "Auto_sync": true,
            "Color": "PEACH"
        }
    ]
}

空表输入示例:

{
    "BrokerID" : "998",
    "AccountID" : "1313",
    "Packages":[]
}

当我使用JsonConvert.DeserializeObject<DataTable>(params["Packages"]);解析它时,我没有得到任何行,显然也没有得到任何列。我正在寻找一种方法来描述JSON主体中的列元数据。

如何在空DataTable的JSON中包含列元数据

Json.Net附带的DataTableConverter不输出列元数据,即使将TypeNameHandling设置为All也是如此。然而,没有什么可以阻止你制作自己的自定义转换器来完成这项工作,并使用它。这是我拼凑的一个可能适合您需求的:
class CustomDataTableConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(DataTable));
    }
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        DataTable dt = (DataTable)value;
        JObject metaDataObj = new JObject();
        foreach (DataColumn col in dt.Columns)
        {
            metaDataObj.Add(col.ColumnName, col.DataType.AssemblyQualifiedName);
        }
        JArray rowsArray = new JArray();
        rowsArray.Add(metaDataObj);
        foreach (DataRow row in dt.Rows)
        {
            JObject rowDataObj = new JObject();
            foreach (DataColumn col in dt.Columns)
            {
                rowDataObj.Add(col.ColumnName, JToken.FromObject(row[col]));
            }
            rowsArray.Add(rowDataObj);
        }
        rowsArray.WriteTo(writer);
    }
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JArray rowsArray = JArray.Load(reader);
        JObject metaDataObj = (JObject)rowsArray.First();
        DataTable dt = new DataTable();
        foreach (JProperty prop in metaDataObj.Properties())
        {
            dt.Columns.Add(prop.Name, Type.GetType((string)prop.Value, throwOnError: true));
        }
        foreach (JObject rowDataObj in rowsArray.Skip(1))
        {
            DataRow row = dt.NewRow();
            foreach (DataColumn col in dt.Columns)
            {
                if (rowDataObj[col.ColumnName].Type != JTokenType.Null)//Skip if the Value is Null/Missing, especially for a non-nullable type.
                    row[col] = rowDataObj[col.ColumnName].ToObject(col.DataType);
            }
            dt.Rows.Add(row);
        }
        return dt;
    }
}

这是一个演示。请注意,当表被序列化时,列类型被写为JSON中数组的第一行。在反序列化时,即使没有其他行,也会使用此元数据以正确的列类型和名称重构表。(您可以通过注释掉顶部将行数据添加到表中的两行来验证这一点。)

class Program
{
    static void Main(string[] args)
    {
        DataTable dt = new DataTable();
        dt.Columns.Add("PackageID", typeof(int));
        dt.Columns.Add("Amount", typeof(int));
        dt.Columns.Add("Auto_sync", typeof(bool));
        dt.Columns.Add("Color", typeof(string));
        // Comment out these two lines to see the table with no data.
        // Test with a null Value for a Non-Nullable DataType.
        dt.Rows.Add(new object[] { 226,  null, true, "BLUE" });
        dt.Rows.Add(new object[] { 500, 15000, true, "PEACH" });
        Foo foo = new Foo
        {
            BrokerID = "998",
            AccountID = "1313",
            Packages = dt
        };
        JsonSerializerSettings settings = new JsonSerializerSettings();
        settings.Converters.Add(new CustomDataTableConverter());
        settings.Formatting = Formatting.Indented;
        string json = JsonConvert.SerializeObject(foo, settings);
        Console.WriteLine(json);
        Console.WriteLine();
        Foo foo2 = JsonConvert.DeserializeObject<Foo>(json, settings);
        Console.WriteLine("BrokerID: " + foo2.BrokerID);
        Console.WriteLine("AccountID: " + foo2.AccountID);
        Console.WriteLine("Packages table:");
        Console.WriteLine("  " + string.Join(", ", 
            foo2.Packages.Columns
                .Cast<DataColumn>()
                .Select(c => c.ColumnName + " (" + c.DataType.Name + ")")));
        foreach (DataRow row in foo2.Packages.Rows)
        {
            Console.WriteLine("  " + string.Join(", ", row.ItemArray
                .Select(v => v != null ? v.ToString() : "(null)")));
        }
    }
}
class Foo
{
    public string BrokerID { get; set; }
    public string AccountID { get; set; }
    public DataTable Packages { get; set; }
}

输出:

{
  "BrokerID": "998",
  "AccountID": "1313",
  "Packages": [
    {
      "PackageID": "System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
      "Amount": "System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
      "Auto_sync": "System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
      "Color": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
    },
    {
      "PackageID": 226,
      "Amount": null,
      "Auto_sync": true,
      "Color": "BLUE"
    },
    {
      "PackageID": 500,
      "Amount": 15000,
      "Auto_sync": true,
      "Color": "PEACH"
    }
  ]
}
BrokerID: 998
AccountID: 1313
Packages table:
  PackageID (Int32), Amount (Int32), Auto_sync (Boolean), Color (String)
  226, , True, BLUE
  500, 15000, True, PEACH

Fiddle:https://dotnetfiddle.net/GGrn9z

您可以在json中添加一个模式:http://json-schema.org/example1.html

你的api方法签名是什么样子的?如果您正在传递一个json字符串以反序列化为对象,那么您就已经拥有了对象本身的列信息。我建议将您的api方法签名更改为以下内容:

[HttpPost, Route("packages")]
public IHttpActionResult Packages(IEnumerable<Package> packages)

然后,您可以将该列表用于数据表,也可以直接枚举该列表。