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

问题描述

我的问题与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 for WriteJson().

  • 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 类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-02 11:40