DateTime列类型在反序列化DataTable之后变为String类型 [英] DateTime column type becomes String type after deserializing DataTable

查看:298
本文介绍了DateTime列类型在反序列化DataTable之后变为String类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个DataTable有两列。装载日期(DateTime)和计数(Int)。在我反序列化字符串之后,我注意到,如果第一个itemarray值为null,则ShipmentDate的类型将变为字符串。



检查以下示例。两个json字符串除了第一个数组项之外都有相同的数据。

  string jsonTable1 =[{\ShipmentDate\ :null,\Count\:3},{\ShipmentDate\:\2015-05-13T00:00:00\,\Count\:13} {\ShipmentDate\:\2015-05-19T00:00:00\,\Count\:1},{\ShipmentDate\:\2015- 05-26T00:00:00\,\Count\:1},{\ShipmentDate\:\2015-05-28T00:00:00\,\ Count\:2}]; 
string jsonTable2 =[{\ShipmentDate\:\2015-05-13T00:00:00\,\Count\:13},{\ \:null,\Count\:3},{\ShipmentDate\:\2015-05-19T00:00:00\,\Count\ 1},{\ShipmentDate\:\2015-05-26T00:00:00\,\Count\:1},{\ShipmentDate\:\ 2015-05-28T00:00:00\,\Count\:2}];

DataTable tbl1 = Newtonsoft.Json.JsonConvert.DeserializeObject< DataTable>(jsonTable1);
DataTable tbl2 = Newtonsoft.Json.JsonConvert.DeserializeObject< DataTable>(jsonTable2);

Console.WriteLine(tbl1.Columns [ShipmentDate]。DataType);
Console.WriteLine(tbl2.Columns [ShipmentDate]。DataType);

在我的场景中,第一个项目数组的装载日期可以为空,并通过将其转换为字符串类型来创建问题



我有一种情况,其中datatable的模式是动态的。我不能创建强类型的类。

解决方案

这里的基本问题是Json.NET的 DataTableConverter 推断每个 DataColumn.DataType 通过查看第一行中存在的令牌值。它以这种方式工作,因为它会流式传输表中的JSON,而不是将整体加载到中间体 JToken 层次结构。虽然流式传输可以减少内存使用的性能,但这意味着第一行中的 null 值可能会导致列错误。



这是一个在stackoverflow中不时出现的问题,例如在问题反序列化具有缺少的第一列的数据表。在这种情况下,提问者事先知道列类型应为 double 。在你的情况下,你已经说明了datatable的schema是动态的,所以答案是不能使用的。不过,就像这个问题一样,由于Json.NET是麻省理工学院许可证,可以创建其 DataTableConverter 具有必要的逻辑。



事实证明,可以正确设置列类型,同时保留流通过记住具有模糊数据类型的列,然后在可以确定适当类型的情况下用正确输入的列替换这些列:

  //< summary> 
///转换< see cref =DataTable/>来自和来自JSON。
///< / summary>
public class TypeInferringDataTableConverter:Newtonsoft.Json.Converters.DataTableConverter
{
//改编自https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft。 Json / Converters / DataTableConverter.cs
//原始许可:https://github.com/JamesNK/Newtonsoft.Json/blob/master/LICENSE.md

///<摘要>
///读取对象的JSON表示。
///< / summary>
///< param name =reader>< see cref =JsonReader/>读取。< / param>
///< param name =objectType>对象的类型< / param>
///< param name =existingValue>正在读取的对象的现有值。< / param>
///< param name =serializer>调用序列化程序< / param>
///< returns>对象值。< / returns>
public override object ReadJson(JsonReader reader,Type objectType,object existingValue,JsonSerializer serializer)
{
if(reader.TokenType == JsonToken.Null)
{
返回null;
}

DataTable dt = existingValue as DataTable;

if(dt == null)
{
//处理类型数据集
dt =(objectType == typeof(DataTable))
? new DataTable()
:(DataTable)Activator.CreateInstance(objectType);
}

// DataTable位于DataSet
内//从属性名称
填入名称if(reader.TokenType == JsonToken.PropertyName)
{
dt.TableName =(string)reader.Value;

reader.ReadAndAssert()

if(reader.TokenType == JsonToken.Null)
{
return dt;
}
}

if(reader.TokenType!= JsonToken.StartArray)
{
throw JsonSerializationExceptionHelper.Create(reader,Unexpected JSON token when读取DataTable,预期的StartArray,得到{0}。FormatWith(CultureInfo.InvariantCulture,reader.TokenType));
}

reader.ReadAndAssert();

var ambiguousColumnTypes = new HashSet< string>();

while(reader.TokenType!= JsonToken.EndArray)
{
CreateRow(reader,dt,serializer,ambiguousColumnTypes);

reader.ReadAndAssert();
}

return dt;
}

private static void CreateRow(JsonReader reader,DataTable dt,JsonSerializer serializer,HashSet< string> ambiguousColumnTypes)
{
DataRow dr = dt.NewRow() ;
reader.ReadAndAssert();

while(reader.TokenType == JsonToken.PropertyName)
{
string columnName =(string)reader.Value;

reader.ReadAndAssert();

DataColumn column = dt.Columns [columnName];
if(column == null)
{
bool isAmbiguousType;

输入columnType = GetColumnDataType(reader,out isAmbiguousType);
column = new DataColumn(columnName,columnType);
dt.Columns.Add(column);

if(isAmbiguousType)
ambiguousColumnTypes.Add(columnName);
}
else if(ambiguousColumnTypes.Contains(columnName))
{
bool isAmbiguousType;
键入newColumnType = GetColumnDataType(reader,out isAmbiguousType);
if(!isAmbiguousType)
ambiguousColumnTypes.Remove(columnName);
if(newColumnType!= column.DataType)
{
column = ReplaceColumn(dt,column,newColumnType,serializer);
}
}

if(column.DataType == typeof(DataTable))
{
if(reader.TokenType == JsonToken.StartArray)
{
reader.ReadAndAssert();
}

DataTable nestedDt = new DataTable();

var nestedUnknownColumnTypes = new HashSet< string>();

while(reader.TokenType!= JsonToken.EndArray)
{
CreateRow(reader,nestedDt,serializer,nestedUnknownColumnTypes);

reader.ReadAndAssert();
}

dr [columnName] = nestedDt;
}
else if(column.DataType.IsArray&&& column.DataType!= typeof(byte []))
{
if(reader.TokenType == JsonToken .StartArray)
{
reader.ReadAndAssert();
}

列表< object> o = new List< object>();

while(reader.TokenType!= JsonToken.EndArray)
{
o.Add(reader.Value);
reader.ReadAndAssert();
}

数组destinationArray = Array.CreateInstance(column.DataType.GetElementType(),o.Count);
Array.Copy(o.ToArray(),destinationArray,o.Count);

dr [columnName] = destinationArray;
}
else
{
对象columnValue =(reader.Value!= null)
? serializer.Deserialize(reader,column.DataType)?? DBNull.Value
:DBNull.Value;

dr [columnName] = columnValue;
}

reader.ReadAndAssert();
}

dr.EndEdit();
dt.Rows.Add(dr);


静态对象RemapValue(object oldValue,Type newType,JsonSerializer serializer)
{
if(oldValue == null)
return null;
if(oldValue == DBNull.Value)
return oldValue;
return JToken.FromObject(oldValue,serializer).ToObject(newType,serializer);
}

private static DataColumn ReplaceColumn(DataTable dt,DataColumn column,Type newColumnType,JsonSerializer serializer)
{
var newValues = Enumerable.Range(0,dt。 Rows.Count).Select(i => dt.Rows [i])。选择(r => RemapValue(r [column],newColumnType,serializer))ToList();

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

var newColumn = new DataColumn(name,newColumnType);
newColumn.Namespace = @namespace;
dt.Columns.Remove(column);
dt.Columns.Add(newColumn);
newColumn.SetOrdinal(ordinal); (int i = 0; i< dt.Rows.Count; i ++)


$ b dt.Rows [i] [newColumn] = newValues [i];

return newColumn;
}

private static类型GetColumnDataType(JsonReader reader,out bool isAmbiguous)
{
JsonToken tokenType = reader.TokenType;

switch(tokenType)
{
case JsonToken.Integer:
case JsonToken.Boolean:
case JsonToken.Float:
case JsonToken .String:
case JsonToken.Date:
case JsonToken.Bytes:
isAmbiguous = false;
return reader.ValueType;
case JsonToken.Null:
case JsonToken.Undefined:
isAmbiguous = true;
return typeof(string);
case JsonToken.StartArray:
reader.ReadAndAssert();
if(reader.TokenType == JsonToken.StartObject)
{
isAmbiguous = false;
return typeof(DataTable); //嵌套的datatable
}
else
{
isAmbiguous = false;
bool innerAmbiguous;
//数组条目中的处理歧义尚未实现,因为数组
//中的第一个非歧义条目可能发生在序列中的任何位置,需要我们扫描整个数组以确定类型,
// eg,给定:[null,null,null,314,null]
//我们需要扫描直到314值,然后执行:
// return typeof(Nullable< ;>)。MakeGenericType(new [] {reader.ValueType})。MakeArrayType();
类型arrayType = GetColumnDataType(reader,out innerAmbiguous);
return arrayType.MakeArrayType();
}
默认值:
throw JsonSerializationExceptionHelper.Create(reader,读取DataTable时出现意外的JSON令牌:{0}。FormatWith(CultureInfo.InvariantCulture,tokenType));
}
}
}

内部静态类JsonSerializationExceptionHelper
{
public static JsonSerializationException Create(this JsonReader reader,string format,params object [] args)
{
//改编自https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/JsonPosition.cs

var lineInfo = reader as IJsonLineInfo;
var path =(reader == null?null:reader.Path);
var message = string.Format(CultureInfo.InvariantCulture,format,args);
if(!message.EndsWith(Environment.NewLine,StringComparison.Ordinal))
{
message = message.Trim();
if(!message.EndsWith(。,StringComparison.Ordinal))
message + =。;
message + =;
}
message + = string.Format(CultureInfo.InvariantCulture,Path{0},path);
if(lineInfo!= null&& lineInfo.HasLineInfo())
message + = string.Format(CultureInfo.InvariantCulture,,line {0},position {1},lineInfo。 LineNumber,lineInfo.LinePosition);
message + =。;

返回新的JsonSerializationException(message);
}
}

内部静态类StringUtils
{
//适用于https://github.com/JamesNK/Newtonsoft.Json/blob /master/Src/Newtonsoft.Json/Utilities/StringUtils.cs
public static string FormatWith(此字符串格式,IFormatProvider提供程序,对象arg0)
{
return format.FormatWith(provider,new [] {arg0});
}

private static string FormatWith(此字符串格式,IFormatProvider提供程序,params对象[] args)
{
return string.Format(provider,format,args );
}
}

内部静态类JsonReaderExtensions
{
public static void ReadAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException(reader);
if(!reader.Read())
{
throw JsonSerializationExceptionHelper.Create(reader,读取JSON时意外结束);
}
}
}

然后使用它: / p>

  var settings = new JsonSerializerSettings {Converters = new [] {new TypeInferringDataTableConverter()}}; 

DataTable tbl1 = Newtonsoft.Json.JsonConvert.DeserializeObject< DataTable>(jsonTable1,settings);
DataTable tbl2 = Newtonsoft.Json.JsonConvert.DeserializeObject< DataTable>(jsonTable2,settings);

不要设置 NullValueHandling = NullValueHandling.Ignore ,因为null现在可以正确处理。 / p>

原型小提琴



请注意,虽然此类处理使用 null 值重新输入列,但它不会处理包含数组值的列的重新输入,第一个数组项为null。例如,如果某列的第一行具有值

  [null,null,null,314,null] 

然后,推断的列类型理想地为 typeof(long?[]),但这并没有在这里实现。很可能需要将JSON完全加载到 JToken 层次结构中才能做出决定。


I have a DataTable having two columns. ShipmentDate(DateTime) and Count(Int). after I deserialize the string I noticed the type of ShipmentDate becomes string if the first itemarray value is null.

Check the below example. both the json string have same data except the first array item.

string jsonTable1 = "[{\"ShipmentDate\":null,\"Count\":3},{\"ShipmentDate\":\"2015-05-13T00:00:00\",\"Count\":13},{\"ShipmentDate\":\"2015-05-19T00:00:00\",\"Count\":1},{\"ShipmentDate\":\"2015-05-26T00:00:00\",\"Count\":1},{\"ShipmentDate\":\"2015-05-28T00:00:00\",\"Count\":2}]";
string jsonTable2 = "[{\"ShipmentDate\":\"2015-05-13T00:00:00\",\"Count\":13},{\"ShipmentDate\":null,\"Count\":3},{\"ShipmentDate\":\"2015-05-19T00:00:00\",\"Count\":1},{\"ShipmentDate\":\"2015-05-26T00:00:00\",\"Count\":1},{\"ShipmentDate\":\"2015-05-28T00:00:00\",\"Count\":2}]";

DataTable tbl1 = Newtonsoft.Json.JsonConvert.DeserializeObject<DataTable>(jsonTable1);
DataTable tbl2 = Newtonsoft.Json.JsonConvert.DeserializeObject<DataTable>(jsonTable2);

Console.WriteLine(tbl1.Columns["ShipmentDate"].DataType);
Console.WriteLine(tbl2.Columns["ShipmentDate"].DataType);

In my scenario ShipmentDate of first item array can be null and it creates problem by converting it to string type.

I have a situation where schema of datatable is dynamic. i can't create strongly typed class.

解决方案

The basic problem here is that Json.NET's DataTableConverter infers each DataColumn.DataType by looking at token values present in the first row only. It works this way because it streams the JSON for the table in rather than loading the entirety into an intermediate JToken hierarchy. While streaming gives better performance with reduced memory use, it means that null values in the first row may result in incorrectly typed columns.

This is a problem that comes up from time to time on stackoverflow, for instance in the question deserialize a datatable with a missing first column. In that case, the questioner knew in advance that the column type should be double. In your case, you have stated schema of datatable is dynamic, so that answer cannot be used. However, as with that question, since Json.NET is open source under the MIT License, one can create a modified version of its DataTableConverter with the necessary logic.

As it turns out, it's possible to correctly set the column type while retaining the streaming behavior by remembering columns with ambiguous data types, then replacing those columns with correctly typed columns when a proper type can be determined:

/// <summary>
/// Converts a <see cref="DataTable"/> to and from JSON.
/// </summary>
public class TypeInferringDataTableConverter : Newtonsoft.Json.Converters.DataTableConverter
{
    // Adapted from https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Converters/DataTableConverter.cs
    // Original license: https://github.com/JamesNK/Newtonsoft.Json/blob/master/LICENSE.md

    /// <summary>
    /// Reads the JSON representation of the object.
    /// </summary>
    /// <param name="reader">The <see cref="JsonReader"/> to read from.</param>
    /// <param name="objectType">Type of the object.</param>
    /// <param name="existingValue">The existing value of object being read.</param>
    /// <param name="serializer">The calling serializer.</param>
    /// <returns>The object value.</returns>
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
        {
            return null;
        }

        DataTable dt = existingValue as DataTable;

        if (dt == null)
        {
            // handle typed datasets
            dt = (objectType == typeof(DataTable))
                ? new DataTable()
                : (DataTable)Activator.CreateInstance(objectType);
        }

        // DataTable is inside a DataSet
        // populate the name from the property name
        if (reader.TokenType == JsonToken.PropertyName)
        {
            dt.TableName = (string)reader.Value;

            reader.ReadAndAssert();

            if (reader.TokenType == JsonToken.Null)
            {
                return dt;
            }
        }

        if (reader.TokenType != JsonToken.StartArray)
        {
            throw JsonSerializationExceptionHelper.Create(reader, "Unexpected JSON token when reading DataTable. Expected StartArray, got {0}.".FormatWith(CultureInfo.InvariantCulture, reader.TokenType));
        }

        reader.ReadAndAssert();

        var ambiguousColumnTypes = new HashSet<string>();

        while (reader.TokenType != JsonToken.EndArray)
        {
            CreateRow(reader, dt, serializer, ambiguousColumnTypes);

            reader.ReadAndAssert();
        }

        return dt;
    }

    private static void CreateRow(JsonReader reader, DataTable dt, JsonSerializer serializer, HashSet<string> ambiguousColumnTypes)
    {
        DataRow dr = dt.NewRow();
        reader.ReadAndAssert();

        while (reader.TokenType == JsonToken.PropertyName)
        {
            string columnName = (string)reader.Value;

            reader.ReadAndAssert();

            DataColumn column = dt.Columns[columnName];
            if (column == null)
            {
                bool isAmbiguousType;

                Type columnType = GetColumnDataType(reader, out isAmbiguousType);
                column = new DataColumn(columnName, columnType);
                dt.Columns.Add(column);

                if (isAmbiguousType)
                    ambiguousColumnTypes.Add(columnName);
            }
            else if (ambiguousColumnTypes.Contains(columnName))
            {
                bool isAmbiguousType;
                Type newColumnType = GetColumnDataType(reader, out isAmbiguousType);
                if (!isAmbiguousType)
                    ambiguousColumnTypes.Remove(columnName);
                if (newColumnType != column.DataType)
                {
                    column = ReplaceColumn(dt, column, newColumnType, serializer);
                }
            }

            if (column.DataType == typeof(DataTable))
            {
                if (reader.TokenType == JsonToken.StartArray)
                {
                    reader.ReadAndAssert();
                }

                DataTable nestedDt = new DataTable();

                var nestedUnknownColumnTypes = new HashSet<string>();

                while (reader.TokenType != JsonToken.EndArray)
                {
                    CreateRow(reader, nestedDt, serializer, nestedUnknownColumnTypes);

                    reader.ReadAndAssert();
                }

                dr[columnName] = nestedDt;
            }
            else if (column.DataType.IsArray && column.DataType != typeof(byte[]))
            {
                if (reader.TokenType == JsonToken.StartArray)
                {
                    reader.ReadAndAssert();
                }

                List<object> o = new List<object>();

                while (reader.TokenType != JsonToken.EndArray)
                {
                    o.Add(reader.Value);
                    reader.ReadAndAssert();
                }

                Array destinationArray = Array.CreateInstance(column.DataType.GetElementType(), o.Count);
                Array.Copy(o.ToArray(), destinationArray, o.Count);

                dr[columnName] = destinationArray;
            }
            else
            {
                object columnValue = (reader.Value != null)
                    ? serializer.Deserialize(reader, column.DataType) ?? DBNull.Value
                    : DBNull.Value;

                dr[columnName] = columnValue;
            }

            reader.ReadAndAssert();
        }

        dr.EndEdit();
        dt.Rows.Add(dr);
    }

    static object RemapValue(object oldValue, Type newType, JsonSerializer serializer)
    {
        if (oldValue == null)
            return null;
        if (oldValue == DBNull.Value)
            return oldValue;
        return JToken.FromObject(oldValue, serializer).ToObject(newType, serializer);
    }

    private static DataColumn ReplaceColumn(DataTable dt, DataColumn column, Type newColumnType, JsonSerializer serializer)
    {
        var newValues = Enumerable.Range(0, dt.Rows.Count).Select(i => dt.Rows[i]).Select(r => RemapValue(r[column], newColumnType, serializer)).ToList();

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

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

        for (int i = 0; i < dt.Rows.Count; i++)
            dt.Rows[i][newColumn] = newValues[i];

        return newColumn;
    }

    private static Type GetColumnDataType(JsonReader reader, out bool isAmbiguous)
    {
        JsonToken tokenType = reader.TokenType;

        switch (tokenType)
        {
            case JsonToken.Integer:
            case JsonToken.Boolean:
            case JsonToken.Float:
            case JsonToken.String:
            case JsonToken.Date:
            case JsonToken.Bytes:
                isAmbiguous = false;
                return reader.ValueType;
            case JsonToken.Null:
            case JsonToken.Undefined:
                isAmbiguous = true;
                return typeof(string);
            case JsonToken.StartArray:
                reader.ReadAndAssert();
                if (reader.TokenType == JsonToken.StartObject)
                {
                    isAmbiguous = false;
                    return typeof(DataTable); // nested datatable
                }
                else
                {
                    isAmbiguous = false;
                    bool innerAmbiguous;
                    // Handling ambiguity in array entries is not yet implemented because the first non-ambiguous entry in the array
                    // might occur anywhere in the sequence, requiring us to scan the entire array to determine the type, 
                    // e.g., given: [null, null, null, 314, null]
                    // we would need to scan until the 314 value, and do:
                    // return typeof(Nullable<>).MakeGenericType(new[] { reader.ValueType }).MakeArrayType();
                    Type arrayType = GetColumnDataType(reader, out innerAmbiguous);
                    return arrayType.MakeArrayType();
                }
            default:
                throw JsonSerializationExceptionHelper.Create(reader, "Unexpected JSON token when reading DataTable: {0}".FormatWith(CultureInfo.InvariantCulture, tokenType));
        }
    }
}

internal static class JsonSerializationExceptionHelper
{
    public static JsonSerializationException Create(this JsonReader reader, string format, params object[] args)
    {
        // Adapted from https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/JsonPosition.cs

        var lineInfo = reader as IJsonLineInfo;
        var path = (reader == null ? null : reader.Path);
        var message = string.Format(CultureInfo.InvariantCulture, format, args);
        if (!message.EndsWith(Environment.NewLine, StringComparison.Ordinal))
        {
            message = message.Trim();
            if (!message.EndsWith(".", StringComparison.Ordinal))
                message += ".";
            message += " ";
        }
        message += string.Format(CultureInfo.InvariantCulture, "Path '{0}'", path);
        if (lineInfo != null && lineInfo.HasLineInfo())
            message += string.Format(CultureInfo.InvariantCulture, ", line {0}, position {1}", lineInfo.LineNumber, lineInfo.LinePosition);
        message += ".";

        return new JsonSerializationException(message);
    }
}

internal static class StringUtils
{
    // Adapted from https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Utilities/StringUtils.cs
    public static string FormatWith(this string format, IFormatProvider provider, object arg0)
    {
        return format.FormatWith(provider, new[] { arg0 });
    }

    private static string FormatWith(this string format, IFormatProvider provider, params object[] args)
    {
        return string.Format(provider, format, args);
    }
}

internal static class JsonReaderExtensions
{
    public static void ReadAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException("reader");
        if (!reader.Read())
        {
            throw JsonSerializationExceptionHelper.Create(reader, "Unexpected end when reading JSON.");
        }
    }
}

Then use it like:

var settings = new JsonSerializerSettings { Converters = new[] { new TypeInferringDataTableConverter() } };

DataTable tbl1 = Newtonsoft.Json.JsonConvert.DeserializeObject<DataTable>(jsonTable1, settings);
DataTable tbl2 = Newtonsoft.Json.JsonConvert.DeserializeObject<DataTable>(jsonTable2, settings);

Do not set NullValueHandling = NullValueHandling.Ignore since nulls are now handled correctly.

Prototype fiddle

Note that, while this class handles re-typing of columns with null values, it doesn't handle re-typing of columns containing array values where the first array item is null. For instance, if the first row of some column has the value

[null, null, null, 314, null]

Then the column type inferred would ideally be typeof( long? [] ), however that is not being implemented here. Likely it would be necessary to completely load the JSON into a JToken hierarchy to make that determination.

这篇关于DateTime列类型在反序列化DataTable之后变为String类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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