我有一个用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看起来像这样:
我已经编写了以下代码来实现这一目标:
#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:
是否可以将代码更改为动态&#34;将JSON转换为所需的DataTable格式?如果是,我该怎么做才能实现这一目标?
提前致谢!
答案 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;通过合并每个细胞的观察类型来进行分类。与仅查看第一行中的单元格相比,这可以处理以下情况:
null
单元格。在这种情况下,要检查后续行。123
的单元格,但后续行包含一个浮点值为12.123
的单元格。在这种情况下,推断类型需要从long
切换为double
或decimal
。1000
的单元格,但后续行包含一个字符串值为"Unknown"
或"$122.22"
(或其他)的单元格。在这种情况下,推断的类型需要切换到string
。long
和DateTime
。在这种情况下会抛出异常。 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表存储 您可以为不同的对象类型使用不同的列名称。
答案 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);