状态属性中的令牌 PropertyName 将导致无效的 JSON 对象。使用自定义 JsonConverter<T> 时

Mar*_*ins 5 c# json.net jsonconverter

我正在尝试使用 Json.NET 和自定义序列化程序序列化/反序列化 .NET DataSet。我知道你们很多人会告诉我不要(我在其他帖子上看到过)我有一个正当的理由并希望继续沿着这条路走下去。

我的序列化基于这样一个事实,即 .NET DataSet 可以将其架构和数据导出到 XML,然后重新导入它们;在此基础上,我正在尝试创建一个转换器,它允许我捕获该 XML,将其转换为 JSON,然后将其转换回来并重新加载它。我的实现如下...

class DataSetConverter : JsonConverter<DataSet>
{
    public override DataSet ReadJson(JsonReader reader, Type objectType, DataSet existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        DataSet dataSet = new DataSet();
        JObject jObject = JObject.Load(reader);

        String json = jObject.ToString();
        XDocument document = JsonConvert.DeserializeXNode(json);
        using (MemoryStream memoryStream = new MemoryStream())
        using (StreamWriter streamWriter = new StreamWriter(memoryStream))
        {
            streamWriter.Write(document.ToString(SaveOptions.None));
            streamWriter.Flush();

            memoryStream.Position = 0;
            dataSet.ReadXml(memoryStream);
        }

        return dataSet;
    }

    public override void WriteJson(JsonWriter writer, DataSet dataSet, JsonSerializer serializer)
    {
        using (MemoryStream memoryStream = new MemoryStream())
        {
            dataSet.WriteXml(memoryStream, XmlWriteMode.WriteSchema);
            using (StreamReader reader = new StreamReader(memoryStream))
            {
                memoryStream.Seek(0, SeekOrigin.Begin);
                XDocument document = XDocument.Parse(reader.ReadToEnd());
                writer.WriteRaw(JsonConvert.SerializeXNode(document, Formatting.Indented, false));
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

使用如下(纯粹序列化一个 DataSet 对象)它的工作原理(我的新 DataSet 与原始数据集具有相同的架构和数据)...

DataSet originalInserts = new DataSet("Inserts");
DataTable originalStuff = originalInserts.Tables.Add("Stuff");

originalStuff.Columns.Add("C1", typeof(String));
originalStuff.Columns.Add("C2", typeof(Int64));
originalStuff.Columns.Add("C3", typeof(Guid));
originalStuff.Columns.Add("C4", typeof(float));

originalStuff.Rows.Add("One", 2, Guid.NewGuid(), 4.4);

String json = JsonConvert.SerializeObject(originalInserts, Formatting.Indented, new DataSetConverter());

DataSet newInsertsFromConvertedXml = (DataSet)JsonConvert.DeserializeObject(json, typeof(DataSet), new DataSetConverter());
Run Code Online (Sandbox Code Playgroud)

但是,如果我然后尝试将相同的转换器与包含DataSet(与DataSet上面相同)的对象一起使用...

public class TestClass
{
    public DataSet Inserts { get; set; }

    public String SomethingElse { get; set; }
}

TestClass testClass = new TestClass { Inserts = originalInserts, SomethingElse = "Me" };
json = JsonConvert.SerializeObject(testClass, Formatting.Indented, new DataSetConverter());
Run Code Online (Sandbox Code Playgroud)

它失败了

状态属性中的令牌 PropertyName 将导致无效的 JSON 对象。小路 ''。

我也曾尝试装饰DataSetTestClass有一个JsonConverter属性和删除Serialize方法调用中的转换器,但得到了同样的结果...

public class TestClass
{
    [JsonConverter(typeof(DataSetConverter))]
    public DataSet Inserts { get; set; }

    public String SomethingElse { get; set; }
}

TestClass testClass = new TestClass { Inserts = originalInserts, SomethingElse = "Me" };
json = JsonConvert.SerializeObject(testClass, Formatting.Indented);
Run Code Online (Sandbox Code Playgroud)

我错过了什么?

dbc*_*dbc 6

你的基本问题是你应该打电话WriteRawValue()而不是WriteRaw()

writer.WriteRawValue(JsonConvert.SerializeXNode(document, Formatting.Indented, false)); 
Run Code Online (Sandbox Code Playgroud)

文档WriteRawValue()状态:

在需要值的地方写入原始 JSON 并更新作者的状态。

文档 WriteRaw()指出:

在不改变作者状态的情况下写入原始 JSON。

未能推进作者的状态解释了为什么在尝试编写后续内容时会抛出异常。

话虽这么说,你要创建一个很多不必要的中间stringStreamJObject你的转换器内部表示。一种更简单的方法是WriteJson()

  1. 构造 anXDocument并将DataSet直接写入它使用XContainer.CreateWriter();

  2. 通过构造一个 local将XDocument直接序列化到传入JsonWriterXmlNodeConverter.

序列化将遵循相反的过程。因此你DataSetConverter会看起来像:

class DataSetConverter : JsonConverter<DataSet>
{
    public override DataSet ReadJson(JsonReader reader, Type objectType, DataSet existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        if (reader.MoveToContent().TokenType == JsonToken.Null)
            return null;
        var converter = new XmlNodeConverter { OmitRootObject = false };
        var document = (XDocument)converter.ReadJson(reader, typeof(XDocument), existingValue, serializer);
        using (var xmlReader = document.CreateReader())
        {
            var dataSet = existingValue ?? new DataSet();
            dataSet.ReadXml(xmlReader);
            return dataSet;
        }
    }

    public override void WriteJson(JsonWriter writer, DataSet dataSet, JsonSerializer serializer)
    {
        var document = new XDocument();
        using (var xmlWriter = document.CreateWriter())
        {
            dataSet.WriteXml(xmlWriter, XmlWriteMode.WriteSchema);
        }
        var converter = new XmlNodeConverter { OmitRootObject = false };
        converter.WriteJson(writer, document, serializer);
    }
}

public static partial class JsonExtensions
{
    public static JsonReader MoveToContent(this JsonReader reader)
    {
        // Start up the reader if not already reading, and skip comments
        if (reader.TokenType == JsonToken.None)
            reader.Read();
        while (reader.TokenType == JsonToken.Comment && reader.Read())
            {}
        return reader;
    }
}
Run Code Online (Sandbox Code Playgroud)

笔记:

  1. 您继承自JsonConverter<DataSet>,并直接ReadJson()构造一个类型的对象DataSet。但是,如参考源所示,JsonConverter<T>.CanConvert(Type objectType)也适用于该类型的所有子类T

    public sealed override bool CanConvert(Type objectType)
    {
        return typeof(T).IsAssignableFrom(objectType);
    }
    
    Run Code Online (Sandbox Code Playgroud)

    因此,您可能需要覆盖CanConvert并使其仅在对象类型等于时应用typeof(DataSet)- 但由于该方法已被密封,因此您不能。因此,可能证明有必要改为从非泛型基类继承JsonConverter