我尝试使用Json.Net将一些动态创建的JSON反序列化为数据表,但结果表没有预期的十进制值。
string data = @"[
{""RowNumber"":1,""ID"":4289,""Assets Variance"":100,""Rules Diff"":10.72,""TotalFunding"":0},
{""RowNumber"":2,""ID"":4233,""Assets Variance"":75,""Rules Diff"":6.7,""TotalFunding"":0},
{""RowNumber"":3,""ID"":2222,""Assets Variance"":43,""Rules Diff"":6.7,""TotalFunding"":43.22}
]";
DataTable dt = JsonConvert.DeserializeObject<DataTable>(data);如果查看此JSON中的前两项,属性Total Funding的值为0,第三项的值为43.22,但是当我们将其转换为数据表时,它将呈现为43。属性Rules Diff不会发生这种情况,因为它在第一个项目本身中有一个有效的十进制值。
JSON中的属性是动态的,因此不能针对特定类型进行强制转换。我们如何对这个JSON进行反序列化,使其在数据表中保留小数?
发布于 2020-04-23 08:55:57
这是Json.Net附带的DataTableConverter的一个已知限制。转换器假定JSON中的第一行数据是所有行的代表性样本,并使用它来确定DataTable中列的数据类型。
如果事先知道JSON中有哪些数据类型,那么解决这个问题的一种方法是反序列化为List<T>而不是DataTable,其中T是一个具有与JSON匹配的属性名称和类型的类。然后,如果您仍然需要一个表,您可以从列表中构建它作为一个后处理步骤。
但是,您说您的JSON是动态的,因此您需要使用自定义的JsonConverter。可以创建一个可以通过JSON进行预读的数据库,以确定每列使用的最佳数据类型。下面这样的代码应该可以工作。您可以根据自己的需要对其进行自定义。
public class ReadAheadDataTableConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DataTable);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JArray array = JArray.Load(reader);
var dataTypes = DetermineColumnDataTypes(array);
var table = BuildDataTable(array, dataTypes);
return table;
}
private DataTable BuildDataTable(JArray array, Dictionary<string, Type> dataTypes)
{
DataTable table = new DataTable();
foreach (var kvp in dataTypes)
{
table.Columns.Add(kvp.Key, kvp.Value);
}
foreach (JObject item in array.Children<JObject>())
{
DataRow row = table.NewRow();
foreach (JProperty prop in item.Properties())
{
if (prop.Value.Type != JTokenType.Null)
{
Type dataType = dataTypes[prop.Name];
row[prop.Name] = prop.Value.ToObject(dataType);
}
}
table.Rows.Add(row);
}
return table;
}
private Dictionary<string, Type> DetermineColumnDataTypes(JArray array)
{
var dataTypes = new Dictionary<string, Type>();
foreach (JObject item in array.Children<JObject>())
{
foreach (JProperty prop in item.Properties())
{
Type currentType = GetDataType(prop.Value.Type);
if (currentType != null)
{
Type previousType;
if (!dataTypes.TryGetValue(prop.Name, out previousType) ||
(previousType == typeof(long) && currentType == typeof(decimal)))
{
dataTypes[prop.Name] = currentType;
}
else if (previousType != currentType)
{
dataTypes[prop.Name] = typeof(string);
}
}
}
}
return dataTypes;
}
private Type GetDataType(JTokenType tokenType)
{
switch (tokenType)
{
case JTokenType.Null:
return null;
case JTokenType.String:
return typeof(string);
case JTokenType.Integer:
return typeof(long);
case JTokenType.Float:
return typeof(decimal);
case JTokenType.Boolean:
return typeof(bool);
case JTokenType.Date:
return typeof(DateTime);
case JTokenType.TimeSpan:
return typeof(TimeSpan);
case JTokenType.Guid:
return typeof(Guid);
case JTokenType.Bytes:
return typeof(byte[]);
case JTokenType.Array:
case JTokenType.Object:
throw new JsonException("This converter does not support complex types");
default:
return typeof(string);
}
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}要使用该转换器,请将一个实例传递给DeserializeObject方法,如下所示:
DataTable dt = JsonConvert.DeserializeObject<DataTable>(data, new ReadAheadDataTableConverter());请注意,由于额外的处理,此转换器的运行速度将比OOB DataTableConverter慢一点。对于较小的数据集,这应该不会引起注意。
发布于 2021-03-05 19:40:09
只是补充一下公认的答案(我没有足够的名气来评论),还有一种情况,你可以将第一个类型作为小数,然后它可能会被检测为整数。@brian-rogers answer会将其放入字符串中。我为这个例子添加了另一个参数:
else if (previousType == typeof(decimal) && currentType == typeof(long))
{
dataTypes[prop.Name] = previousType;
}下面是他的修改过的演示:https://dotnetfiddle.net/IxGerR
https://stackoverflow.com/questions/61372252
复制相似问题