C#从不完全已知的JSON创建DataTable

时间:2018-04-18 11:38:45

标签: c# asp.net json parsing azure-functions

我有一个用C#编写的Azure函数,它通过HTTP POST请求接收JSON,其中我确定节点:ssid,dim,type,list_of_business_keys。在收到JSON之前,列表中的每个项目都有一个或多个列,其中包含我不知道的名称和类型。让我们说第一个例子是column_1和column_2,两者都是Int64类型:

{ "ssid" : 1, 
  "dim" : 2,
  "type" : 3,
  "list_of_business_keys":
  [
      {"business_key" : {"column_1" : 100, "column_2" : 1000}},
      {"business_key" : {"column_1" : 200, "column_2" : 1000}},
      {"business_key" : {"column_1" : 300, "column_2" : 1000}},
      {"business_key" : {"column_1" : 400, "column_2" : 1000}},
      {"business_key" : {"column_1" : 500, "column_2" : 1000}}
  ]
}

我想要实现的是将此JSON转换为DataTable,我稍后将其用作表类型参数来从Azure SQL数据库调用存储过程。所以我希望这个DataTable看起来像这样:

enter image description here

我已经编写了以下代码来实现这一目标:

#r "Microsoft.WindowsAzure.Storage"
#r "Newtonsoft.Json"
#r "System.Net"
#r "System.Data"

using System;
using System.Net;
using System.Data;
using System.Data.SqlClient;
using Microsoft.WindowsAzure.Storage.Table;
using Newtonsoft.Json;

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
    string resultAsString = await req.Content.ReadAsStringAsync();

    KeyList keyList = JsonConvert.DeserializeObject<KeyList>(resultAsString);

    List<ListOfBusinessKey> list = keyList.list_of_business_keys;

    DataTable tbl = new DataTable();

    tbl.Columns.Add(new DataColumn("ssid", typeof(Int64)));
    tbl.Columns.Add(new DataColumn("dim", typeof(Int64)));
    tbl.Columns.Add(new DataColumn("type", typeof(Int64)));
    tbl.Columns.Add(new DataColumn("column_1", typeof(Int64)));
    tbl.Columns.Add(new DataColumn("column_2", typeof(Int64)));



    foreach (var key in list) {
        tbl.Rows.Add(keyList.ssid, keyList.dim, keyList.type, key.business_key.column_1, key.business_key.column_2);  
    }

    foreach (var row in tbl.Rows){
        log.Info(row.ToString());
    }

    foreach (DataRow dataRow in tbl.Rows)
    {
        foreach (var item in dataRow.ItemArray)
        {
            log.Info(item.ToString());
        }
    }

    return req.CreateResponse(keyList);

}

public class BusinessKey
{
    public int column_1 { get; set; }
    public int column_2 { get; set; }
}

public class ListOfBusinessKey
{
    public BusinessKey business_key { get; set; }
}

public class KeyList
{
    public int ssid { get; set; }
    public int dim { get; set; }
    public int type { get; set; }
    public List<ListOfBusinessKey> list_of_business_keys { get; set; }
}

这似乎有效。

我的问题是要反序列化收到的JSON,我必须根据JSON的结构创建类。创建数据表也是如此。我知道col名称及其类型,因此我隐式创建了DataTable。但是,当我以前不知道结构时会发生什么?

现在让我假设我收到以下JSON:

{ "ssid" : 1, 
  "dim" : 2,
  "type" : 3,
  "list_of_business_keys":
  [
      {"business_key" : {"xxx" : "abc", "yyy" : 1000, "zzz" : 123}},
      {"business_key" : {"xxx" : "cde", "yyy" : 1000, "zzz" : 456}},
      {"business_key" : {"xxx" : "efg", "yyy" : 1000, "zzz" : 789}},
      {"business_key" : {"xxx" : "hij", "yyy" : 1000, "zzz" : 12 }},
      {"business_key" : {"xxx" : "klm", "yyy" : 1000, "zzz" : 345}}
  ]
}

我希望收到以下DT:

enter image description here

是否可以将代码更改为动态&#34;将JSON转换为所需的DataTable格式?如果是,我该怎么做才能实现这一目标?

提前致谢!

3 个答案:

答案 0 :(得分:1)

您可以将business_key定义为Dictionary<string, object>,然后根据JSON文件中实际遇到的属性名称和数据类型动态填充DataTable

定义以下类型和扩展方法:

public class ListOfBusinessKey
{
    public Dictionary<string, object> business_key { get; set; }
}

public class KeyList
{
    public int ssid { get; set; }
    public int dim { get; set; }
    public int type { get; set; }
    public List<ListOfBusinessKey> list_of_business_keys { get; set; }

    public DataTable ToDataTable()
    {
        var tbl = new DataTable();
        tbl.Columns.Add(new DataColumn("ssid", typeof(Int64)));
        tbl.Columns.Add(new DataColumn("dim", typeof(Int64)));
        tbl.Columns.Add(new DataColumn("type", typeof(Int64)));

        var columnQuery = EnumerableExtensions.Merge(
            list_of_business_keys
            .SelectMany(k => k.business_key)
            .Select(p => new KeyValuePair<string, Type>(p.Key, p.Value == null ? typeof(object) : p.Value.GetType())),
            p => p.Key, (p1, p2) => new KeyValuePair<string, Type>(p1.Key, MergeTypes(p1.Value, p2.Value)));
        foreach (var c in columnQuery)
            tbl.Columns.Add(c.Key, c.Value);

        foreach (var d in list_of_business_keys.Select(k => k.business_key))
        {
            var row = tbl.NewRow();
            row["ssid"] = ssid;
            row["dim"] = dim;
            row["type"] = type;
            foreach (var p in d.Where(p => p.Value != null))
            {
                row[p.Key] = Convert.ChangeType(p.Value, tbl.Columns[p.Key].DataType, CultureInfo.InvariantCulture);
            }
            tbl.Rows.Add(row);
        }
        return tbl;
    }

    static Type MergeTypes(Type type1, Type type2)
    {
        // Enhance as needed
        if (type1 == type2)
            return type1;
        if (type2 == typeof(object))
            return type1;
        if (type1 == typeof(object))
            return type2;
        if (type1.IsAssignableFrom(type2))
            return type1;
        if (type2.IsAssignableFrom(type1))
            return type2;
        if (typeof(IConvertible).IsAssignableFrom(type1) && typeof(IConvertible).IsAssignableFrom(type2))
        {
            if (type1 == typeof(string))
                return type1;
            if (type2 == typeof(string))
                return type2;
            if ((type1 == typeof(long) || type1 == typeof(int)) && (type2 == typeof(decimal) || type2 == typeof(double)))
                return type2;
            if ((type2 == typeof(long) || type2 == typeof(int)) && (type1 == typeof(decimal) || type1 == typeof(double)))
                return type1;
        }
        throw new ArgumentException(string.Format("Cannot merge types {0} and {1}", type1, type2));
    }
}

public static class EnumerableExtensions
{
    public static IEnumerable<TSource> Merge<TSource, TKey>(IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TSource, TSource> mergeSelector)
    {
        if (source == null || keySelector == null || mergeSelector == null)
            throw new ArgumentNullException();
        return MergeIterator(source, keySelector, mergeSelector);
    }

    static IEnumerable<TSource> MergeIterator<TSource, TKey>(IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TSource, TSource> mergeSelector)
    {
        var dictionary = new Dictionary<TKey, TSource>();
        foreach (TSource element in source)
        {
            var key = keySelector(element);
            TSource oldElement;
            if (!dictionary.TryGetValue(key, out oldElement))
            {
                dictionary[key] = element;
            }
            else
            {
                dictionary[key] = mergeSelector(element, oldElement);
            }
        }
        return dictionary.Values;
    }
}

然后反序列化并转换为DataTable,如下所示:

var keyList = JsonConvert.DeserializeObject<KeyList>(json);
var tbl = keyList.ToDataTable();

请注意,处理第一次遇到空值的"business_key"属性之一以及后来遇到非空值,或者首先遇到整数值然后遇到的情况时,需要一些尴尬带小数或双精度值。在这种情况下,更普遍的类型需要优先于最初遇到的类型。

示例工作.Net fiddle

<强>更新

EnumerableExtensions.Merge()MergeTypes()的目的是通过查看将添加到列中的所有单元格来推断用于Type的正确DataColumn.DataType选择一个最合适的&#34;通过合并每个细胞的观察类型来进行分类。与仅查看第一行中的单元格相比,这可以处理以下情况:

  1. 第一行包含null单元格。在这种情况下,要检查后续行。
  2. 第一行包含一个整数值为123的单元格,但后续行包含一个浮点值为12.123的单元格。在这种情况下,推断类型需要从long切换为doubledecimal
  3. 第一行包含一个数字值为1000的单元格,但后续行包含一个字符串值为"Unknown""$122.22"(或其他)的单元格。在这种情况下,推断的类型需要切换到string
  4. 将添加到同一列的两个单元格具有完全不兼容的类型,例如longDateTime。在这种情况下会抛出异常。
  5. Newtonsoft自己的DataTableConverter仅从第一行推断DataColumn.DataType,这会导致问题,例如来自 DateTime column type becomes String type after deserializing DataTable 的问题deserialize a datatable with a missing first column 的。此答案中的代码利用了将整个JSON预加载到JToken层次结构中以避免这些问题的事实。

    example fiddle中的第三个样本JSON字符串包含案例#1-#3的样本。

    如果您确定上述任何一种情况都不会发生,您可以简化MergeTypes()以便在类型不相同时抛出异常。

答案 1 :(得分:0)

它闻起来像NoSql数据库的解决方案。例如Azure表存储 您可以为不同的对象类型使用不同的列名称。

Azure table storage store multiple types

答案 2 :(得分:0)

首先创建将jsonstring转换为datatable的函数:

   public DataTable JsonStringToDataTable(string jsonString)
   {
      DataTable dt = new DataTable();
      string[] jsonStringArray = Regex.Split(jsonString.Replace("[", "").Replace("]", ""), "},{");
      List<string> ColumnsName = new List<string>();
      foreach (string jSA in jsonStringArray)
      {
         string[] jsonStringData = Regex.Split(jSA.Replace("{", "").Replace("}", ""), ",");
         foreach (string ColumnsNameData in jsonStringData)
         {
            try
            {
               int idx = ColumnsNameData.IndexOf(":");
               string ColumnsNameString = ColumnsNameData.Substring(0, idx - 1).Replace("\"", "");
               if (!ColumnsName.Contains(ColumnsNameString))
               {
                  ColumnsName.Add(ColumnsNameString);
               }
            }
            catch (Exception ex)
            {
               throw new Exception(string.Format("Error Parsing Column Name : {0}", ColumnsNameData));
            }
         }
         break;
      }
      foreach (string AddColumnName in ColumnsName)
      {
         dt.Columns.Add(AddColumnName);
      }
      foreach (string jSA in jsonStringArray)
      {
         string[] RowData = Regex.Split(jSA.Replace("{", "").Replace("}", ""), ",");
         DataRow nr = dt.NewRow();
         foreach (string rowData in RowData)
         {
            try
            {
               int idx = rowData.IndexOf(":");
               string RowColumns = rowData.Substring(0, idx - 1).Replace("\"", "");
               string RowDataString = rowData.Substring(idx + 1).Replace("\"", "");
               nr[RowColumns] = RowDataString;
            }
            catch (Exception ex)
            {
               continue;
            }
         }
         dt.Rows.Add(nr);
      }
      return dt;
   }

然后调用此函数:

 string FileName = "JSONString.txt";
 var stream = File.OpenText(Server.MapPath(FileName));
 string JsonString = stream.ReadToEnd();
 DataTable dt = JsonStringToDataTable(JsonString);