反序列化缺少第一列的数据表

Posted

技术标签:

【中文标题】反序列化缺少第一列的数据表【英文标题】:deserialize a datatable with a missing first column 【发布时间】:2015-12-20 00:17:50 【问题描述】:

我正在尝试将 .NET DataTable 序列化为 JSON 文件,然后将 JSON 文件反序列化回 DataTable。我认为相当简单。

但是,我有一个 3 行 3 列的表格,每个元素都是双精度类型。如果第一行中的任何值为空,当 JSON.Net 将 json 文件反序列化为 DataTable 对象时,第一行中为空的列的所有值都变为字符串。

需要明确的是,只有第一行中的值为 null 时才会发生这种情况。如果除第一行之外的任何其他行中的任何值为 null,则该列中的剩余值保持双倍。

    如果我将 null 替换为双精度值,一切都会按预期工作(但在我的情况下,我不能这样做)。

    如果我设置 NullValueHandling = NullValueHandling.Ignore,所有值都保持为双精度值,除了第一行现在被列为最后一行:

例子:

"Column2": 1.0,
"Column3": 1.1
,

   "Column1": 0.0,
   "Column2": 0.5,
   "Column3": 2.0
,

变成:

  "Column2": 1.0,
  "Column3": 1.1
,

  "Column2": 0.5,
  "Column3": 2.0,
  "Column1": 0.0
,

我需要能够反序列化 JSON,保持列有序,并且第一行中没有空值会导致该行中的所有值都变为字符串。我还需要保持第一行的 Column1(在上述情况下)为空 - 不关心它是空字符串还是 DBNull。

有什么想法吗? (我的测试代码下面..comment/uncomment NullValueHandling 看问题)

        DataTable table = new DataTable("MyTable");
        table.Columns.Add("Column1", typeof(double));
        table.Columns.Add("Column2", typeof(double));
        table.Columns.Add("Column3", typeof(double));

        for (int i = 0; i < 10; i++) 
            if (i == 0)
                table.Rows.Add(null, 1.0, 1.1);
            else
               table.Rows.Add(0.0, 0.5, 2.0);
        

        JsonSerializer serializer = new JsonSerializer();
        //serializer.TypeNameHandling = TypeNameHandling.All;
        serializer.NullValueHandling = NullValueHandling.Ignore;
        using (StreamWriter sw1 = new StreamWriter("1st.json"))
        using (JsonWriter writer1 = new JsonTextWriter(sw1))
        
            writer1.Formatting = Formatting.Indented;
            serializer.Serialize(writer1, table);
        

        DataTable newtable;
        using (StreamReader sr = new StreamReader("1st.json"))
        using (JsonReader reader = new JsonTextReader(sr))
        
            newtable = (DataTable)serializer.Deserialize(reader, typeof(DataTable));
        

        using (StreamWriter sw = new StreamWriter("3rd.json"))
        using (JsonWriter writer = new JsonTextWriter(sw))
        
            writer.Formatting = Formatting.Indented;
            serializer.Serialize(writer, newtable);
        

【问题讨论】:

我建议你应该创建一个类并序列化/反序列化到那个而不是 DataTable 如果可能的话。 感谢您的建议,但是在整个项目中都抛出 DataTable 时,我必须将 100k 行代码项目从 XML 转换为 json,这是不可能的(这是一个可怕的设计,但你并不总是可以选择)。同时,我将尝试其他几个 json 库。希望有人有一个想法..我会对它发生的“为什么”感到满意。顺便说一句……上面的代码是一个令人尴尬的简单示例,我编写它是为了验证它是 JSON.net,而不是代码库中的其他交互。二维数组或列表会更容易。 Json.NET 在MIT license 下开源,因此您可以复制和修改其代码。创建DataTableConverter 的一个版本,例如DoubleDataTableConverter,其中static Type GetColumnDataType(JsonReader reader) 为空值返回typeof(double) 您可以使用this answer 来防止丢失的列类型 【参考方案1】:

Json.NET 在MIT License 下是开源的,因此一种可能性是创建其DataTableConverter 的修改版本以满足您的需求。其源代码可在here 获得。

首先,创建您自己的这个类的分支版本,例如,称为JsonDefaultTypeDataTableConverter&lt;T&gt;。修改方法GetColumnDataType 为空列值返回typeof(T)

/// <summary>
/// Converts a <see cref="DataTable"/> to and from JSON.
/// Adapted from https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Converters/DataTableConverter.cs
/// </summary>
public class JsonDefaultTypeDataTableConverter<T>  : JsonConverter

    private static Type GetColumnDataType(JsonReader reader)
    
        JsonToken tokenType = reader.TokenType;

        switch (tokenType)
        
            case JsonToken.Integer:
            case JsonToken.Boolean:
            case JsonToken.Float:
            case JsonToken.String:
            case JsonToken.Date:
            case JsonToken.Bytes:
                return reader.ValueType;
            case JsonToken.Null:
            case JsonToken.Undefined:
                return typeof(T); // WAS typeof(string).
            case JsonToken.StartArray:
                CheckedRead(reader);
                if (reader.TokenType == JsonToken.StartObject)
                
                    return typeof (DataTable); // nested datatable
                

                Type arrayType = GetColumnDataType(reader);
                return arrayType.MakeArrayType();
            default:
                throw new JsonSerializationException(string.Format("Unexpected JSON token when reading DataTable: 0", tokenType));
        
    

您还需要修复在232 附近抛出JsonSerializationException 的调用,例如如下:

    private static void CheckedRead(JsonReader reader)
    
        if (!reader.Read())
        
            throw new JsonSerializationException(string.Format("Unexpected end when reading DataTable."));
        
    

而且,在114 附近:

        if (reader.TokenType != JsonToken.StartArray)
        
            throw new JsonSerializationException(string.Format("Unexpected JSON token when reading DataTable. Expected StartArray, got 0.", reader.TokenType));
        

现在,鉴于您知道您的表包含 double 值的列,您可以像这样使用它:

        JsonSerializer serializer = new JsonSerializer();
        //serializer.TypeNameHandling = TypeNameHandling.All;
        //serializer.NullValueHandling = NullValueHandling.Ignore; -- DO NOT USE THIS OPTION.
        serializer.Converters.Add(new JsonDefaultTypeDataTableConverter<double>());

请注意,在执行此操作时,您并没有修改 Json.NET 本身的内部结构,而是复制和修改了其一组标准 converters 中常用的 .Net 类型之一。

更新:完整版here.

【讨论】:

这是转换器的完整版本:pastebin.com/S79bacwe。它基于 VS 2008 或更高版本构建。您无需重新构建所有 Json.NET 即可使用它,只需将此文件添加到本地解决方案即可。 非常感谢!这解决了问题。如果我使用 NullValueHandling.Ignore,你知道为什么它仍会将 Column1 作为最后一列吗?我对此进行了评论并获得了预期的行为,但最好仍然将其用于序列化类的其他部分。 @Dime - 我 think* 发生这种情况是因为 written json 缺少一列,这会在读取时弄乱列顺序。您可以从 JsonDefaultTypeDataTableConverter&lt;T&gt;.WriteJson() 中删除 if (serializer.NullValueHandling == NullValueHandling.Ignore &amp;&amp; (columnValue == null || columnValue == DBNull.Value))

以上是关于反序列化缺少第一列的数据表的主要内容,如果未能解决你的问题,请参考以下文章

检测反序列化的对象是不是缺少 Json.NET 中 JsonConvert 类的字段

反序列化自定义 XML 时出现 InvalidOperationException(缺少命名空间)

c# XmlSerializer 反序列化器缺少默认命名空间

使用 protobuf-net 反序列化不同的列表

ASP.Net Core 中的 JSON 序列化/反序列化

Spring Jackson反序列化只选择数组的第一项