当前几个JSON项有非十进制值时,动态JSON到DataTable的反序列化会丢失小数。

我试图使用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,使其在数据表中保留小数?

解决方案:

这是一个已知的限制 DataTableConverter Json.Net中的数据类型。 转换器假设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 由于额外的处理。 对于小数据集,它应该是不明显的。

工作演示在这里。https:/dotnetfiddle.netiZ0u6Y。

给TA打赏
共{{data.count}}人
人已打赏
未分类

ag-grid复制到剪贴板,具有格式化样式功能。

2022-9-10 11:33:21

未分类

VBA For Each Loop to Excel JavaScript API代码

2022-9-10 11:33:23

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索