您对我们如何使用 System.Text.Json.JsonSerializer 序列化 DataSet、DataTable 有 什么建议吗?
目前它抛出此异常:'检测到不支持的可能对象循环。这可能是由于循环或对象深度大于最大允许深度 64 造成的。
3 回答
DataSet
目前没有对像和DataTable
in这样的类型的内置支持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
这是 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; }
}
目前不支持使用 System.Text.Json 进行引用循环处理。解决方法:
https://www.thecodebuzz.com/jsonexception-possible-object-cycle-detected-object-depth/
您可以在违规属性上添加自定义转换器来处理引用自身的对象,或者只需将 JsonIgnore 属性添加到违规属性。