簡體   English   中英

當前幾個 JSON 項目具有非十進制值時,將動態 JSON 反序列化為 DataTable 會丟失小數

[英]Deserialization of dynamic JSON to a DataTable is losing decimals when first few JSON items have non-decimal values

我正在嘗試使用 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 以便它在數據表中保留小數?

這是 Json.Net 附帶的DataTableConverter的已知限制。 轉換器假定 JSON 中的第一行數據是所有行的代表性樣本,並使用它來確定DataTable中列的數據類型。

如果您事先知道 JSON 中有哪些數據類型,則解決此問題的一種方法是反序列化為List<T>而不是DataTable ,其中T是 class ,其屬性名稱和類型與 Z0ECD11C1D7A287402 匹配。 然后,如果您仍然需要一個表,您可以從列表中構建它作為后處理步驟。

但是,您說您的 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.net/iZ0u6Y

只是為了添加已接受的答案(我沒有足夠的聲譽來評論),還有一種情況是您可以將第一種類型作為小數,然后可以將其檢測為 integer。 @brian-rogers 答案會將其放入字符串中。 我為這種情況添加了另一個 elseif:

else if (previousType == typeof(decimal) && currentType == typeof(long))
{
   dataTypes[prop.Name] = previousType;
}

這是他修改后的演示: https://dotnetfiddle.net/IxGerR

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM