Newtonsoft JSON/JSON.NET 自定义序列化 - System.Object 成员即使在设置 TypeNameHandling 时也未正确反序列化

Newtonsoft JSON/JSON.NET custom serialization - System.Object members not correctly deserialized even when setting TypeNameHandling

我正在尝试使用 Newtonsoft.Json 实现自定义序列化,我希望所有字段都序列化,然后反序列化为它们的正确类型。 class 包含类型 "object" 的字段,实现 ISerializable,具有 [Serializable] 属性集,并具有序列化构造函数。我把这个对象字段的值设置为某个class的一个实例,然后序列化它。我使用 JsonSerializer 并将 TypeNameHandling 设置为 TypeNameHandling.Auto

这是我正在尝试的代码:

using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.IO;
using System.Runtime.Serialization;

namespace ConsoleApp1
{
    class Program
    {
        [Serializable]
        class Foobar : ISerializable
        {
            public Foobar()
            {
            }

            public Foobar(SerializationInfo info, StreamingContext context)
            {
                something = info.GetValue("something", typeof(object));
            }

            public void GetObjectData(SerializationInfo info, StreamingContext context)
            {
                info.AddValue("something", something);
            }

            public object Something { get { return something; } set { something = value; } }
            private object something;

            [JsonIgnore]
            private string someOtherObject = "foobar";
        }

        class SomeOtherClass
        {
            public string Foo { get; set; }
            public string Bar { get; set; }
        }

        static void Main(string[] args)
        {
            Foobar myObj = new Foobar();

            myObj.Something = new SomeOtherClass() { Bar = "My first", Foo = "fqwkifjwq" };

            var serializer = new Newtonsoft.Json.JsonSerializer();
            serializer.TypeNameHandling = TypeNameHandling.Auto;
            serializer.Formatting = Formatting.Indented;
            string serialized;
            using (var tw = new StringWriter())
            {
                using (var jw = new JsonTextWriter(tw))
                    serializer.Serialize(jw, myObj);

                tw.Flush();
                serialized = tw.ToString();
            }

            Foobar deserialized;
            using (var rt = new StringReader(serialized))
            using (var jsonReader = new JsonTextReader(rt))
                deserialized = serializer.Deserialize<Foobar>(jsonReader);

            Console.WriteLine("Type of deserialized.Something: " + deserialized.Something.GetType().FullName);
        }
    }
}

反序列化后,字段 Foobar.Something 只是一个 Newtonsoft.Json.Linq.JObject,这不是我想要的。我希望将其正确反序列化为 SomeOtherClass 类型的对象。序列化输出确实包含所需信息:

{
  "something": {
    "$type": "ConsoleApp1.Program+SomeOtherClass, ConsoleApp1",
    "Foo": "fqwkifjwq",
    "Bar": "My first"
  }
}

这是我到目前为止尝试过的方法:

  1. 使用上面的代码。完全按照我上面的描述去做。
  2. 在我正在序列化的对象上使用 [JsonObject(MemberSerialization = MemberSerialization.Fields)] 属性。然后我在 Foobar.Something 字段(一个 SomeOtherClass 实例)上得到一个正确类型的对象,但是,任何具有默认值的非序列化字段都被初始化为 null 而不是它们的默认值(比如字段 Foobar.someOtherObject).
  3. 正在删除 Serializable 属性。然后 ISerializable GetObjectData 和序列化构造函数不被调用。
  4. 将 JsonSerializer 的 TypeNameHandling 属性 设置为 All(无效)。

所以 - 关于如何解决这个问题的任何提示?

所以,总结一下你的 question/comments:

  1. 您有一个庞大而复杂的遗留数据结构,您希望对其进行序列化和反序列化。
  2. 您想要 de/serialize 的大部分数据都在私有字段中,向这些字段添加属性或添加 public 属性太麻烦了。
  3. 其中一些字段的类型为 object,您希望为往返保留这些值的类型。
  4. 为了序列化目的,其中一些字段被明确标记为忽略,并且它们具有您希望保留的默认值。
  5. 您尝试使用 TypeNameHandling.Auto 并实施 ISerializable;这适用于序列化(子对象类型被写入 JSON)但不适用于反序列化(你得到一个 JObject 返回而不是实际的子对象实例)。
  6. 您尝试使用 TypeNameHandling.Auto 并用 MemberSerialization.Fields 标记您的外部 class;这适用于序列化,但在反序列化时,您丢失了忽略字段的默认值。

让我们看看这两种方法,看看我们能做些什么。

ISerializable

当 Json.Net 确实将这些子对象的类型信息写入 JSON关于连载。我不知道这是错误、疏忽,还是这里有一些技术限制。无论如何,它的行为并不像预期的那样。

但是,您可以实施解决方法。由于您确实在 SerializationInfo 中得到了 JObject,并且 JObject 中包含 $type 字符串,因此您可以创建一个扩展方法,该方法将从JObject 基于 $type:

public static class SerializationInfoExtensions
{
    public static object GetObject(this SerializationInfo info, string name)
    {
        object value = info.GetValue(name, typeof(object));
        if (value is JObject)
        {
            JObject obj = (JObject)value;
            string typeName = (string)obj["$type"];
            if (typeName != null)
            {
                Type type = Type.GetType(typeName);
                if (type != null)
                {
                    value = obj.ToObject(type);
                }
            }
        }
        return value;
    }
}

然后,只要类型为 object:

,就在序列化构造函数中使用它代替 GetValue
public Foobar(SerializationInfo info, StreamingContext context)
{
    something = info.GetObject("something");
}

MemberSerialization.Fields

看起来使用 [JsonObject(MemberSerialization = MemberSerialization.Fields)] 属性几乎可以满足您的所有需求,只是会丢失一些您忽略的属性的默认值。事实证明,Json.Net 在使用 MemberSerialization.Fields 时故意不调用普通构造函数——而是使用 FormatterServices.GetUninitializedObject 方法创建一个 完全 空填充 JSON 中的字段之前的对象。这显然会阻止您的默认值被初始化。

要解决此问题,您可以使用自定义 ContractResolver 来替换对象创建函数,例如:

class FieldsOnlyResolver : DefaultContractResolver
{
    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        JsonObjectContract contract = base.CreateObjectContract(objectType);
        contract.DefaultCreator = () => Activator.CreateInstance(objectType, true);
        return contract;
    }
}

注意:以上假定您的所有对象都将具有默认(无参数)构造函数。如果不是这种情况,您可以在需要的地方添加它们(它们可以是私有的),或者更改解析器以根据类型提供不同的创建者函数。

要使用它,只需将解析器添加到您的 JsonSerializer 实例:

serializer.ContractResolver = new FieldsOnlyResolver();