更改数字反序列化的默认类型 [英] Change default type of numeric deserialization

查看:67
本文介绍了更改数字反序列化的默认类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我将一些JSON数据反序列化为DataSet时,结果数据集可能会丢失其列架构.这意味着,当我反序列化某些JSON时,它将使用Int64对象而不是Int32填充数据集.我希望它选择Int32.

While I was deserializing some JSON data to DataSet, the resultant dataset may lose its column schema. that means, When I deserialize some JSON, it populates the Dataset with Int64 objects rather than Int32. I would like it to choose Int32.

我知道,Json.NET默认将整数值读取为Int64,因为无法知道该值应为Int32还是Int64.

I know, Json.NET by default reads integer values as Int64 because there is no way to know whether the value should be Int32 or Int64.

JsonSerializerSettings settings = new JsonSerializerSettings()
    {
        Converters = { new PrimitiveJsonConverter() },
    };
DataSet myDataSet = JsonConvert.DeserializeObject<DataSet>(jsonString, settings);

因此,我创建了自定义JsonConverter,以覆盖默认功能.

So I have created custom JsonConverter, to override default functionality.

using DevExpress.XtraPrinting.Native.WebClientUIControl;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization.Formatters;
using System.Text;
using System.Threading.Tasks;
using JsonConverter = Newtonsoft.Json.JsonConverter;

namespace CashlessAdmin.API.Handler
{
    public sealed class PrimitiveJsonConverter : JsonConverter
    {
        readonly JsonSerializer defaultSerializer = new JsonSerializer();

        public override bool CanConvert(Type objectType)
        {
            return objectType.IsIntegerTypes();

        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            switch (reader.TokenType)
            {
                case JsonToken.Integer:
                    if(Convert.ToInt64(reader.Value) < System.Int32.MaxValue)
                    {
                        return Convert.ToInt32(reader.Value);
                    }
                    return reader.Value;
                case JsonToken.Float: // Accepts numbers like 4.00
                case JsonToken.Null:
                    return defaultSerializer.Deserialize(reader, objectType);
                default:
                    throw new JsonSerializationException(string.Format("Token \"{0}\" of type {1} was not a JSON integer", reader.Value, reader.TokenType));
            }
        }

        public override bool CanWrite { get { return false; } }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }

    public static class JsonExtensions
    {
        public static bool IsIntegerTypes(this Type type)
        {
            type = Nullable.GetUnderlyingType(type) ?? type;
            if (type == typeof(long)
                || type == typeof(ulong)
                || type == typeof(int)
                || type == typeof(uint)
                || type == typeof(short)
                || type == typeof(ushort)
                || type == typeof(byte)
                || type == typeof(sbyte)
                || type == typeof(System.Numerics.BigInteger))
                return true;
            return false;
        }
    }
}

但是结果将与前面的情况相同.

But the result will be the same as the previous case.

推荐答案

您的代码无法正常工作的原因是,在最初推断列类型时,是 JsonReader.Read() ,然后在

The reason your code does not work is that, when initially inferring column type, DataTableConverter does not attempt to deserialize the first value encountered for a column. Instead, it simply reads it using JsonReader.Read() and then sets the column type equal to the observed token type, in DataTableConverter.GetColumnDataType(). Your method PrimitiveJsonConverter.Read() just isn't getting called at this point. And, since JsonReader.Read() is designed to return a long instead of an int for integer values, the data table column types end up as long.

您可以选择一些选项来覆盖Newtonsoft的默认行为并获取Int32列类型:

You have a few options to override Newtonsoft's default behavior and get Int32 column types:

  1. 您可以使用

  1. You could use a typed DataSet. In this situation the column types will be predefined.

您可以使用PreferInt32JsonTextReader此答案中读取

You could read using PreferInt32JsonTextReader from this answer to Overriding Default Primitive Type Handling in Json.Net (Json.NET 10.0.1 or later).

您可以在反序列化之后将列转换为Int32.首先,介绍以下扩展方法:

You could convert columns to Int32 after deserialization. First, introduce the following extension method:

public static class DataTableExtensions
{
    public static DataTable RemapInt64ColumnsToInt32(this DataTable table)
    {
        if (table == null)
            throw new ArgumentNullException();
        for (int iCol = 0; iCol < table.Columns.Count; iCol++)
        {
            var col = table.Columns[iCol];
            if (col.DataType == typeof(Int64)
                && table.AsEnumerable().Where(r => !r.IsNull(col)).Select(r => (Int64)r[col]).All(i => i >= int.MinValue && i <= int.MaxValue))
            {
                ReplaceColumn(table, col, typeof(Int32), (o, t) => o == null ? null : Convert.ChangeType(o, t, NumberFormatInfo.InvariantInfo));
            }
        }
        return table;
    }

    private static DataColumn ReplaceColumn(DataTable table, DataColumn column, Type newColumnType, Func<object, Type, object> map)
    {
        var newValues = table.AsEnumerable()
            .Select(r => r.IsNull(column) ? (object)DBNull.Value : map(r[column], newColumnType))
            .ToList();

        var ordinal = column.Ordinal;
        var name = column.ColumnName;
        var @namespace = column.Namespace;

        var newColumn = new DataColumn(name, newColumnType);
        newColumn.Namespace = @namespace;
        table.Columns.Remove(column);
        table.Columns.Add(newColumn);
        newColumn.SetOrdinal(ordinal);

        for (int i = 0; i < table.Rows.Count; i++)
            if (!(newValues[i] is DBNull))
                table.Rows[i][newColumn] = newValues[i];

        return newColumn;
    }    
}

然后执行:

var myDataSet = JsonConvert.DeserializeObject<DataSet>(json);
myDataSet.Tables.Cast<DataTable>().Aggregate((object)null, (o, dt) => dt.RemapInt64ColumnsToInt32());

相关: 如何更改数据表中数据列的数据类型? .

您可以派生自己的 DataTableConverter 并修改

You could fork your own version of DataTableConverter and modify the logic of DataTableConverter.GetColumnDataType() to return typeof(Int32) for JsonToken.Integer tokens.

有关所涉及内容的示例,请参见 ="a href =" https:"此答案://stackoverflow.com/q/32726718/3744182>反序列化缺少第一列的数据表 .

For an example of what would be involved, see this answer to deserialize a datatable with a missing first column.

由于您的根对象是DataSet,因此您还需要派生自己的 在自定义类中对DataTable属性进行反序列化之后,DateTime列类型变为String类型 a> .

Since your root object is a DataSet, you would also need to fork your own version of DataSetConverter and make it use your customized DataTableConverter, as shown in this answer to DateTime column type becomes String type after deserializing DataTable property on Custom Class.

OP 请求其性能如何...?

您必须对其进行测试并查看,请参见 https://ericlippert.com/2012/12/17/performance-rant/.

You have to test it and see, see https://ericlippert.com/2012/12/17/performance-rant/.

也就是说,通常,对于庞大的数据集,您要避免在最终反序列化之前,以某种中间表示(例如JToken层次结构或单个大string)将整个数据集加载到内存中.选项#1,#2和#4避免这样做. #3确实将部分数据加载到中间表示中;某些(但不是全部)DataTable列最终被加载然后被替换.因此性能可能还可以,但可能不行-您需要检查.

That being said, in general, with huge data sets, you want to avoid loading the entire data set into memory in some intermediate representation (e.g. a JToken hierarchy or single large string) before final deserialization. Options #1, #2 and #4 avoid doing so. #3 does load a portion of the data into an intermediate representation; some but not all DataTable columns end up being loaded and then replaced. Thus performance may be OK, but maybe not -- you need to check.

这篇关于更改数字反序列化的默认类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆