【问题标题】:Token PropertyName in state Property would result in an invalid JSON object. when using custom JsonConverter<T>状态 Property 中的 Token PropertyName 会导致 JSON 对象无效。使用自定义 JsonConverter<T> 时
【发布时间】:2026-02-06 18:00:02
【问题描述】:

我正在尝试使用 Json.NET 和自定义序列化程序对 .NET 数据集进行序列化/反序列化。我知道你们中的许多人会告诉我不要(我在其他帖子上看到过)我有充分的理由并希望继续沿着这条路线前进。

我的序列化是基于 .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));
            }
        }
    }
}

按如下方式使用(纯粹序列化一个 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());

但是,如果我随后尝试将相同的转换器与包含 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());

它失败了

状态属性中的令牌属性名称将导致无效的 JSON 目的。路径''。

我还尝试使用JsonConverter 属性在TestClass 上装饰DataSet,并从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);

我错过了什么?

【问题讨论】:

    标签: c# json.net jsonconverter


    【解决方案1】:

    您的基本问题是您应该调用WriteRawValue() 而不是WriteRaw()

    writer.WriteRawValue(JsonConvert.SerializeXNode(document, Formatting.Indented, false)); 
    

    WriteRawValue()documentation 声明:

    在预期值的地方写入原始 JSON 并更新写入器的状态。

    documentation WriteRaw() 声明:

    在不更改写入器状态的情况下写入原始 JSON。

    未能推进写入者的状态解释了为什么在尝试写入后续内容时会引发异常。

    话虽如此,您在转换器内部创建了 很多 不必要的中间 stringStreamJObject 表示形式。一个更简单的方法是,在WriteJson() 中:

    1. 构造一个XDocument 并使用XContainer.CreateWriter()DataSet 直接写入它;

    2. 通过构造本地XmlNodeConverter,将XDocument直接序列化为传入的JsonWriter

    序列化将遵循相反的过程。因此,您的 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;
        }
    }
    

    注意事项:

    1. 你继承自JsonConverter&lt;DataSet&gt;,在ReadJson()中你直接构造了一个DataSet类型的对象。但是,如reference source 中所示,JsonConverter&lt;T&gt;.CanConvert(Type objectType) 也适用于T 类型的所有子类

      public sealed override bool CanConvert(Type objectType)
      {
          return typeof(T).IsAssignableFrom(objectType);
      }
      

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

    【讨论】:

    • 我最终确实找到了 WriteRawValue 并使用它与 WritePropertyName 一起启动并运行了一个工作版本,但是您的版本要干净得多。谢谢你这么简洁的回答。
    最近更新 更多