反序列化DataTable后,DateTime列类型变为String类型
我有一个DataTable有两列。 ShipmentDate(DateTime)和Count(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},{\"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(jsonTable1); DataTable tbl2 = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonTable2); Console.WriteLine(tbl1.Columns["ShipmentDate"].DataType); Console.WriteLine(tbl2.Columns["ShipmentDate"].DataType);
在我的场景中,第一个项目数组的ShipmentDate可以为null,并通过将其转换为字符串类型来创建问题。
我有一种情况,数据表的模式是动态的。 我不能创建强类型的类。
这里的基本问题是Json.NET的DataTableConverter
通过查看第一行中出现的标记值来推断每个DataColumn.DataType
。 它以这种方式工作,因为它为表中的JSON流而不是将整体加载到中间JToken
层次结构中。 虽然流式传输在内存使用减少的情况下提供了更好的性能,但这意味着第一行中的null
值可能会导致列类型不正确。
这是stackoverflow上不时出现的问题,例如在问题中反序列化缺少第一列的数据表 。 在这种情况下,提问者事先知道列类型应该是double
。 在您的情况下,您已声明数据表的模式是动态的 ,因此无法使用答案。 但是,与该问题一样,由于Json.NET是MIT许可下的开源,因此可以使用必要的逻辑创建其DataTableConverter
的修改版本。
事实certificate,通过记住具有不明确数据类型的列,可以正确设置列类型,同时保留流行为,然后在确定正确类型时用正确键入的列替换这些列:
/// /// Converts a to and from JSON. /// 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 /// /// Reads the JSON representation of the object. /// /// The to read from. /// Type of the object. /// The existing value of object being read. /// The calling serializer. /// The object value. 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(); 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 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 (); 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
然后使用它像:
var settings = new JsonSerializerSettings { Converters = new[] { new TypeInferringDataTableConverter() } }; DataTable tbl1 = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonTable1, settings); DataTable tbl2 = Newtonsoft.Json.JsonConvert.DeserializeObject (jsonTable2, settings);
不要设置NullValueHandling = NullValueHandling.Ignore
因为现在可以正确处理空值。
原型小提琴
请注意,虽然此类处理具有null
值的列的重新键入,但它不会处理包含第一个数组项为null的数组值的列的重新键入。 例如,如果某列的第一行具有该值
[null, null, null, 314, null]
那么推断出的列类型最好是typeof( long? [] )
,但是这里没有实现。 可能有必要将JSON完全加载到JToken
层次结构中以进行确定。