问题描述
我的问题与this非常相似一个但是我没有足够的声誉来对原始答案发表评论.
我有一个名为 FillPDF 的自定义类,我正在服务器上对其进行序列化并在客户端进行反序列化.
FillPDF dsPDF = JsonConvert.DeserializeObject(json);
FillPDF
类包含一个 DataSet
属性,该属性包含一组 DataTables
通过阅读原始问题的解决方案,我知道为什么 DateTime
类型被错误地设置为 String
.我了解 Json.Net 的 DataTableConverter
通过仅查看第一行来推断每个 DataColumn.DataType
(我的第一行具有 NULL
值).>
我试图从原始问题中实施解决方案.Dbc 建议覆盖 DataTableConverter
.我已经这样做了,我在序列化和反序列化期间使用 settings
对象,如下所示:
//服务器FillPDF pdfData = new FillPDF(strUniqueColID);var settings = new JsonSerializerSettings { Converters = new[] { new TypeInferringDataTableConverter() } };字符串 json = JsonConvert.SerializeObject(pdfData, Formatting.Indented,settings);//客户var settings = new JsonSerializerSettings { Converters = new[] { new TypeInferringDataTableConverter() } };FillPDF dsPDF = JsonConvert.DeserializeObject(json,settings);
但是我没有收到任何错误,我的底层数据表仍然没有被正确反序列化.我认为这是因为我正在序列化/反序列化一个自定义对象,而不是像原始问题中那样简单的 DataTable
.
我希望能够做的是:
if(c.ColumnName.toLower().Contains("date")){//将列的类型设置为 DateTime 因为我知道所有包含date"的列名称都应该是 DateTime 类型}
据推测,这必须添加到覆盖的 TypeInferringDataTableConverter
中.
我不太确定从哪里开始,所以我非常需要一些帮助,所以我转向 SO!
谢谢,
贾斯汀.
问题 似乎是 Newtonsoft 的 DataSetConverter
使用 Newtonsoft 的 通过执行 DataTableConverterConverter = new DataTableConverter();
然后直接调用它的 ReadJson()
方法.因此,您的转换器从未被使用过.
一种解决方案是通过改编 James Newton-King 的 :
公共类DataSetConverter: DataSetConverter 其中 TDataTableConverter : JsonConverter, new(){//此代码改编自//https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Converters/DataSetConverter.cs//版权所有 (c) 2007 James Newton-King//根据麻省理工学院许可 (MIT) 获得许可://https://github.com/JamesNK/Newtonsoft.Json/blob/master/LICENSE.md只读 TDataTableConverter 转换器 = 新 TDataTableConverter();公共覆盖对象 ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer){if (reader.TokenType == JsonToken.Null){返回空;}//处理类型化数据集数据集 ds = (objectType == typeof(DataSet))?新数据集(): (DataSet)Activator.CreateInstance(objectType);reader.ReadAndAssert();while (reader.TokenType == JsonToken.PropertyName){DataTable dt = ds.Tables[(string)reader.Value];bool 存在 = (dt != null);dt = (DataTable)converter.ReadJson(reader, typeof(DataTable), dt, serializer);如果(!存在){ds.Tables.Add(dt);}reader.ReadAndAssert();}返回 ds;}}公共静态类 JsonReaderExtensions{public static void ReadAndAssert(这个JsonReader阅读器){如果(读者==空)抛出新的 ArgumentNullException();如果 (!reader.Read()){new JsonReaderException(string.Format("Unexpected end at path {0}", reader.Path));}}}
然后将 DataSetConverter
添加到您的转换器列表中.
顺便说一句,如果您需要做的只是在列名称包含字符串 "date"
时将列类型设置为 DateTime
,您可能会考虑创建一个更简单的转换器比 TypeInferringDataTableConverter
沿着转换器的行从 反序列化缺少第一列的数据表:
fork
DataTableConverter
.记下开头的许可://特此免费授予任何人权限//获取此软件和相关文档的副本//文件(软件"),在不处理软件的情况下//限制,包括但不限于使用权,//复制、修改、合并、发布、分发、再许可和/或出售//软件的副本,并允许向其提供//提供软件以实现此目的,但须符合以下条件//条件:////以上版权声明和本许可声明为//包含在软件的所有副本或重要部分中.////...
让你的分叉转换器子类 Newtonsoft 的
DataTableConverter
;删除WriteJson()
的所有代码.修改
GetColumnDataType()
,传入列名并添加必要的逻辑:私有静态类型 GetColumnDataType(JsonReader reader, string columnName){JsonToken tokenType = reader.TokenType;开关(令牌类型){案例 JsonToken.String:if (columnName.IndexOf("date", StringComparison.OrdinalIgnoreCase) >= 0)返回类型(日期时间);返回 reader.ValueType;案例 JsonToken.Integer:案例 JsonToken.Boolean:案例 JsonToken.Float:案例 JsonToken.Date:案例 JsonToken.Bytes:返回 reader.ValueType;案例 JsonToken.Null:案例 JsonToken.Undefined:if (columnName.IndexOf("date", StringComparison.OrdinalIgnoreCase) >= 0)返回类型(日期时间);返回类型(字符串);案例 JsonToken.StartArray:reader.ReadAndAssert();if (reader.TokenType == JsonToken.StartObject){返回类型(数据表);//嵌套数据表}类型 arrayType = GetColumnDataType(reader, columnName);返回 arrayType.MakeArrayType();默认:throw JsonSerializationException.Create(reader, "读取数据表时出现意外的 JSON 标记:{0}".FormatWith(CultureInfo.InvariantCulture, tokenType));}}
然后修复对
GetColumnDataType()
在第 152 行周围传入列名:Type columnType = GetColumnDataType(reader, columnName);
在任何缺少的内部方法中存根,例如
ReadAndAssert()
和静态扩展方法,如图所示这里.
另一种解决方案来创建您自己的 Newtonsoft 转换器版本,在 [OnDeserialized]
容器类中的事件,循环遍历DataSet
中所有表的所有列并转换使用来自如何更改数据表中数据列的数据类型?.
My question is very similar to this one however I don't have enough reputation to post a comment on the original answer.
I've got a custom class called FillPDF that I'm serializing on the server and deserializing on the client.
FillPDF dsPDF = JsonConvert.DeserializeObject<FillPDF>(json);
The FillPDF
class consists of a DataSet
property which contains a collection of DataTables
From reading the solution to the original question I'm aware of why the DateTime
type is improperly being set as a String
. I understand Json.Net's DataTableConverter
infers each DataColumn.DataType
by looking at the first row only (my first row has NULL
values).
I tried to implement the solution from the original question. Dbc had suggested overriding the DataTableConverter
. I've done it as such and I'm using the settings
object during Serialization and Deserialization like so:
// Server
FillPDF pdfData = new FillPDF(strUniqueColID);
var settings = new JsonSerializerSettings { Converters = new[] { new TypeInferringDataTableConverter() } };
string json = JsonConvert.SerializeObject(pdfData, Formatting.Indented,settings);
// Client
var settings = new JsonSerializerSettings { Converters = new[] { new TypeInferringDataTableConverter() } };
FillPDF dsPDF = JsonConvert.DeserializeObject<FillPDF>(json,settings);
However I'm not getting any errors and my underlying DataTables are still not being correctly Deserialized. I assume this is because I'm serializing/deserializing a custom object as opposed to simply a DataTable
like in the original question.
What I would like to be able to do is :
if(c.ColumnName.toLower().Contains("date"))
{
// Set Column's Type to DateTime because I know all Column Names containing "date" should be of type DateTime
}
Presumably this would have to be added to the overridden TypeInferringDataTableConverter
.
I'm not too sure where to go from here, so I'm turning to SO in dire need of some help!
Thanks,
Justin.
The problem seems to be that Newtonsoft's DataSetConverter
hardcodes using Newtonsoft's DataTableConverter
by doing DataTableConverter converter = new DataTableConverter();
and then calling its ReadJson()
method directly. Thus your converter is never used.
One solution would be to create your own version of DataSetConverter
as well by adapting James Newton-King's original code:
public class DataSetConverter<TDataTableConverter> : DataSetConverter where TDataTableConverter : JsonConverter, new()
{
// This code adapted from
// https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Converters/DataSetConverter.cs
// Copyright (c) 2007 James Newton-King
// Licensed under The MIT License (MIT):
// https://github.com/JamesNK/Newtonsoft.Json/blob/master/LICENSE.md
readonly TDataTableConverter converter = new TDataTableConverter();
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
// handle typed datasets
DataSet ds = (objectType == typeof(DataSet))
? new DataSet()
: (DataSet)Activator.CreateInstance(objectType);
reader.ReadAndAssert();
while (reader.TokenType == JsonToken.PropertyName)
{
DataTable dt = ds.Tables[(string)reader.Value];
bool exists = (dt != null);
dt = (DataTable)converter.ReadJson(reader, typeof(DataTable), dt, serializer);
if (!exists)
{
ds.Tables.Add(dt);
}
reader.ReadAndAssert();
}
return ds;
}
}
public static class JsonReaderExtensions
{
public static void ReadAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (!reader.Read())
{
new JsonReaderException(string.Format("Unexpected end at path {0}", reader.Path));
}
}
}
Then add DataSetConverter<TypeInferringDataTableConverter>
to your list of converters.
Incidentally, if all you need to do is to set the column type to DateTime
when the column name includes the string "date"
, you might consider creating a simpler converter than TypeInferringDataTableConverter
along the lines of the converter from deserialize a datatable with a missing first column:
Fork the code of
DataTableConverter
. Take note of the license at the beginning:Have your forked converter subclass Newtonsoft's
DataTableConverter
; remove all code forWriteJson()
.Modify
GetColumnDataType()
to pass in the column name and add the necessary logic:private static Type GetColumnDataType(JsonReader reader, string columnName) { JsonToken tokenType = reader.TokenType; switch (tokenType) { case JsonToken.String: if (columnName.IndexOf("date", StringComparison.OrdinalIgnoreCase) >= 0) return typeof(DateTime); return reader.ValueType; case JsonToken.Integer: case JsonToken.Boolean: case JsonToken.Float: case JsonToken.Date: case JsonToken.Bytes: return reader.ValueType; case JsonToken.Null: case JsonToken.Undefined: if (columnName.IndexOf("date", StringComparison.OrdinalIgnoreCase) >= 0) return typeof(DateTime); return typeof(string); case JsonToken.StartArray: reader.ReadAndAssert(); if (reader.TokenType == JsonToken.StartObject) { return typeof(DataTable); // nested datatable } Type arrayType = GetColumnDataType(reader, columnName); return arrayType.MakeArrayType(); default: throw JsonSerializationException.Create(reader, "Unexpected JSON token when reading DataTable: {0}".FormatWith(CultureInfo.InvariantCulture, tokenType)); } }
Then fix the call to
GetColumnDataType()
to pass in the column name around line 152:Type columnType = GetColumnDataType(reader, columnName);
Stub in any missing internal methods such as
ReadAndAssert()
with static extensions methods as shown here.
An alternate solution to creating your own versions of Newtonsoft's converters would be, in an [OnDeserialized]
event in the container class, to loop through all columns in all tables in the DataSet
and convert columns of type string
(or object
) whose name contains "date"
to DateTime
columns, using one of the answers from How To Change DataType of a DataColumn in a DataTable?.
这篇关于在自定义类上反序列化 DataTable 属性后,DateTime 列类型变为 String 类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!