2

您对我们如何使用 System.Text.Json.JsonSerializer 序列化 DataSet、DataTable 有 什么建议? 目前它抛出此异常:'检测到不支持的可能对象循环。这可能是由于循环或对象深度大于最大允许深度 64 造成的。

4

3 回答 3

8

DataSet目前没有对像和DataTablein这样的类型的内置支持System.Text.Json(从 .NET Core 3.1 开始)。为了能够序列化这些类型,您需要为JsonConverter<T>您需要的类型实现自己的类型并将其注册到JsonSerializerOptions. 为您要求的特定类型编写一个序列化应该相当容易。

这是一个适用于序列化的示例(省略了反序列化组件):

public class DataTableConverter : JsonConverter<DataTable>
{
    public override DataTable Read(ref Utf8JsonReader reader, Type typeToConvert,
        JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }

    public override void Write(Utf8JsonWriter writer, DataTable value,
        JsonSerializerOptions options)
    {
        writer.WriteStartArray();

        foreach (DataRow row in value.Rows)
        {
            writer.WriteStartObject();
            foreach (DataColumn column in row.Table.Columns)
            {
                object columnValue = row[column];

                // If necessary:
                if (options.IgnoreNullValues)
                {
                    // Do null checks on the values here and skip writing.
                }

                writer.WritePropertyName(column.ColumnName);
                JsonSerializer.Serialize(writer, columnValue, options);
            }
            writer.WriteEndObject();
        }

        writer.WriteEndArray();
    }
}

public class DataSetConverter : JsonConverter<DataSet>
{
    public override DataSet Read(ref Utf8JsonReader reader, Type typeToConvert,
        JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }

    public override void Write(Utf8JsonWriter writer, DataSet value,
        JsonSerializerOptions options)
    {
        writer.WriteStartObject();
        foreach (DataTable table in value.Tables)
        {
            writer.WritePropertyName(table.TableName);
            JsonSerializer.Serialize(writer, table, options);
        }
        writer.WriteEndObject();
    }
}

private static void DataSet_Serialization_WithSystemTextJson()
{
    var options = new JsonSerializerOptions()
    {
        Converters = { new DataTableConverter(), new DataSetConverter() }
    };

    (DataTable table, DataSet dataSet) = GetDataSetAndTable();

    string jsonDataTable = JsonSerializer.Serialize(table, options);
    // [{"id":0,"item":"item 0"},{"id":1,"item":"item 1"}]
    Console.WriteLine(jsonDataTable);

    string jsonDataSet = JsonSerializer.Serialize(dataSet, options);
    // {"Table1":[{"id":0,"item":"item 0"},{"id":1,"item":"item 1"}]}
    Console.WriteLine(jsonDataSet);

    // Local function to create a sample DataTable and DataSet
    (DataTable, DataSet) GetDataSetAndTable()
    {
        dataSet = new DataSet("dataSet");

        table = new DataTable();
        DataColumn idColumn = new DataColumn("id", typeof(int))
        {
            AutoIncrement = true
        };

        DataColumn itemColumn = new DataColumn("item");

        table.Columns.Add(idColumn);
        table.Columns.Add(itemColumn);

        dataSet.Tables.Add(table);

        for (int i = 0; i < 2; i++)
        {
            DataRow newRow = table.NewRow();
            newRow["item"] = "item " + i;
            table.Rows.Add(newRow);
        }

        dataSet.AcceptChanges();

        return (table, dataSet);
    }
}

本文档可能会提供更多指导: https ://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to

于 2020-01-17T03:29:18.663 回答
1

这是 DataTable 和 DataSet 自定义转换器的代码。它仅支持常见/简单类型,这是降低面临序列化器/反序列化器漏洞风险的好方法。

namespace MyJsonConverters
{
    public class DataTableJsonConverter : JsonConverter<DataTable>
    {
        public override DataTable Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            DataTable dt = new DataTable();
            while (reader.Read())
            {
                if (reader.TokenType == JsonTokenType.EndObject)
                    return dt;

                if (reader.TokenType != JsonTokenType.PropertyName)
                    throw new JsonException();

                string propertyName = reader.GetString();
                if (propertyName == "TableName")
                {
                    dt.TableName = JsonSerializer.Deserialize<string>(ref reader, options);
                }
                else if (propertyName == "Columns")
                {
                    JsonDataColumn[] jsonDataColumns = JsonSerializer.Deserialize<JsonDataColumn[]>(ref reader, options);
                    foreach (JsonDataColumn jsonDataColumn in jsonDataColumns)
                    {
                        Type dataType = Type.GetType(jsonDataColumn.DataType);
                        dt.Columns.Add(jsonDataColumn.ColumnName, dataType);
                        if (jsonDataColumn.MaxLength > 0)
                            dt.Columns[dt.Columns.Count].MaxLength = jsonDataColumn.MaxLength;
                    }
                }
                else if (propertyName == "Rows")
                {
                    JsonElement[][] jsonRows = JsonSerializer.Deserialize<JsonElement[][]>(ref reader, options);
                    foreach (JsonElement[] jsonElements in jsonRows)
                    {
                        DataRow drNew = dt.NewRow();
                        int i = -1;
                        foreach(DataColumn dc in dt.Columns)
                        {
                            i++;

                            if (jsonElements[i].ToString() == "{}") // DBNull
                                continue;
                            else if (dc.DataType == typeof(string))
                                drNew[dc] = jsonElements[i].GetString();
                            else if (dc.DataType == typeof(bool))
                                drNew[dc] = jsonElements[i].GetBoolean();
                            else if (dc.DataType == typeof(DateTime))
                                drNew[dc] = jsonElements[i].GetDateTime();
                            else if (dc.DataType == typeof(Int64))
                                drNew[dc] = jsonElements[i].GetInt64();
                            else if (dc.DataType == typeof(Int32))
                                drNew[dc] = jsonElements[i].GetInt32();
                            else if (dc.DataType == typeof(Int16))
                                drNew[dc] = jsonElements[i].GetInt16();
                            else if (dc.DataType == typeof(UInt64))
                                drNew[dc] = jsonElements[i].GetUInt64();
                            else if (dc.DataType == typeof(UInt32))
                                drNew[dc] = jsonElements[i].GetUInt32();
                            else if (dc.DataType == typeof(UInt16))
                                drNew[dc] = jsonElements[i].GetUInt16();
                            else if (dc.DataType == typeof(byte))
                                drNew[dc] = jsonElements[i].GetByte();
                            else if (dc.DataType == typeof(sbyte))
                                drNew[dc] = jsonElements[i].GetSByte();
                            else if (dc.DataType == typeof(Single))
                                drNew[dc] = jsonElements[i].GetSingle();
                            else if (dc.DataType == typeof(Double))
                                drNew[dc] = jsonElements[i].GetDouble();
                            else if (dc.DataType == typeof(Decimal))
                                drNew[dc] = jsonElements[i].GetDecimal();
                            else if (dc.DataType == typeof(Guid))
                                drNew[dc] = jsonElements[i].GetGuid();
                            else if (dc.DataType == typeof(byte[]))
                                drNew[dc] = jsonElements[i].GetBytesFromBase64();
                            else
                                throw new JsonException("Column data type not supported in DataTableJsonConverter: " + dc.DataType);
                        }
                        dt.Rows.Add(drNew);
                    }
                }
            }
            throw new JsonException();
        }

        public override void Write(Utf8JsonWriter writer, DataTable dt, JsonSerializerOptions options)
        {
            JsonDataTable j = new JsonDataTable
            {
                TableName = dt.TableName,
                Columns = dt.Columns.Cast<DataColumn>().Select(c => new JsonDataColumn { ColumnName = c.ColumnName, DataType = c.DataType.ToString(), MaxLength = c.MaxLength }).ToArray(),
                Rows = dt.Rows.Cast<DataRow>().Select(dr => dr.ItemArray).ToArray()
            };
            JsonSerializer.Serialize(writer, j, options);
        }


        public class JsonDataTable
        {
            public string TableName { get; set; }
            public JsonDataColumn[] Columns { get; set; }
            public object[][] Rows { get; set; }
        }

        public class JsonDataColumn
        {
            public string ColumnName { get; set; }
            public string DataType { get; set; }
            public int MaxLength { get; set; }
        }
    }

    public class DataSetJsonConverter : JsonConverter<DataSet>
    {
        public override DataSet Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            DataTableJsonConverter dataTableJsonConverter = new DataTableJsonConverter(); 
            DataSet ds = new DataSet();
            while (reader.Read())
            {
                if (reader.TokenType == JsonTokenType.EndObject)
                    return ds;

                if (reader.TokenType != JsonTokenType.PropertyName)
                    throw new JsonException();

                string propertyName = reader.GetString();
                if (propertyName == "DataSetName")
                {
                    ds.DataSetName = JsonSerializer.Deserialize<string>(ref reader, options);
                }
                else if (propertyName == "Tables")
                {
                    reader.Read();
                    if (reader.TokenType != JsonTokenType.StartArray)
                        throw new JsonException();
                    while (reader.Read())
                    {
                        if (reader.TokenType == JsonTokenType.EndArray)
                            break;
                        DataTable dt = dataTableJsonConverter.Read(ref reader, null, options);
                        ds.Tables.Add(dt);
                    }  
                }
            }
            throw new JsonException();
        }

        public override void Write(Utf8JsonWriter writer, DataSet ds, JsonSerializerOptions options)
        {
            DataTableJsonConverter dataTableJsonConverter = new DataTableJsonConverter();

            writer.WriteStartObject();
            writer.WritePropertyName("DataSetName");
            writer.WriteStringValue(ds.DataSetName);
            writer.WritePropertyName("Tables");
            writer.WriteStartArray();
            foreach(DataTable dt in ds.Tables)
                dataTableJsonConverter.Write(writer, dt, options);
            writer.WriteEndArray();
            writer.WriteEndObject();
        }
    }
}

用法:

public class MyClass
{
    [System.Text.Json.Serialization.JsonConverter(typeof(MyJsonConverters.DataTableJsonConverter))] 
    public DataTable DataTable { get; set; }
    [System.Text.Json.Serialization.JsonConverter(typeof(MyJsonConverters.DataSetJsonConverter))]
    public DataSet DataSet { get; set; }
}
于 2021-07-05T15:34:45.723 回答
1

目前不支持使用 System.Text.Json 进行引用循环处理。解决方法:

检测到.Net Core 3.0 可能的对象循环,但不支持

https://www.thecodebuzz.com/jsonexception-possible-object-cycle-detected-object-depth/

您可以在违规属性上添加自定义转换器来处理引用自身的对象,或者只需将 JsonIgnore 属性添加到违规属性。

于 2021-01-12T11:25:26.157 回答