Десериализация динамических c JSON в DataTable теряет десятичные дроби, когда первые несколько JSON элементов имеют недесятичные значения - PullRequest
1 голос
/ 22 апреля 2020

Я пытаюсь десериализовать некоторые динамически созданные JSON в таблицу данных, используя Json. Net, и полученная таблица не имеет ожидаемых десятичных значений.

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 являются динамическими c и, следовательно, приведение к указанному типу c не является опцией. Как мы можем десериализовать этот JSON, чтобы он сохранил десятичные дроби в таблице данных?

1 Ответ

1 голос
/ 23 апреля 2020

Это известное ограничение для DataTableConverter, поставляемого с Json. Net. Преобразователь предполагает, что первая строка данных в JSON является репрезентативной выборкой для всех строк, и использует ее для определения типов данных для столбцов в DataTable.

Если вы заранее знаете, что Типы данных, которые есть в вашем JSON, один из способов обойти эту проблему - десериализовать в List<T> вместо DataTable, где T - это класс с именами свойств и типами, соответствующими JSON. Затем, если вам все еще нужна таблица, вы можете создать ее из списка в качестве этапа последующей обработки.

Однако вы сказали, что ваш JSON - это Dynami c, поэтому вам нужно будет использовать пользовательский 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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...