首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >当前几个JSON项具有非十进制值时,动态JSON到DataTable的反序列化将丢失小数

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

Stack Overflow用户
提问于 2020-04-23 02:16:03
回答 2查看 613关注 0票数 3

我尝试使用Json.Net将一些动态创建的JSON反序列化为数据表,但结果表没有预期的十进制值。

代码语言:javascript
复制
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进行反序列化,使其在数据表中保留小数?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-04-23 08:55:57

这是Json.Net附带的DataTableConverter的一个已知限制。转换器假定JSON中的第一行数据是所有行的代表性样本,并使用它来确定DataTable中列的数据类型。

如果事先知道JSON中有哪些数据类型,那么解决这个问题的一种方法是反序列化为List<T>而不是DataTable,其中T是一个具有与JSON匹配的属性名称和类型的类。然后,如果您仍然需要一个表,您可以从列表中构建它作为一个后处理步骤。

但是,您说您的JSON是动态的,因此您需要使用自定义的JsonConverter。可以创建一个可以通过JSON进行预读的数据库,以确定每列使用的最佳数据类型。下面这样的代码应该可以工作。您可以根据自己的需要对其进行自定义。

代码语言:javascript
复制
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方法,如下所示:

代码语言:javascript
复制
DataTable dt = JsonConvert.DeserializeObject<DataTable>(data, new ReadAheadDataTableConverter());

请注意,由于额外的处理,此转换器的运行速度将比OOB DataTableConverter慢一点。对于较小的数据集,这应该不会引起注意。

工作演示:https://dotnetfiddle.net/iZ0u6Y

票数 5
EN

Stack Overflow用户

发布于 2021-03-05 19:40:09

只是补充一下公认的答案(我没有足够的名气来评论),还有一种情况,你可以将第一个类型作为小数,然后它可能会被检测为整数。@brian-rogers answer会将其放入字符串中。我为这个例子添加了另一个参数:

代码语言:javascript
复制
else if (previousType == typeof(decimal) && currentType == typeof(long))
{
   dataTypes[prop.Name] = previousType;
}

下面是他的修改过的演示:https://dotnetfiddle.net/IxGerR

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/61372252

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档