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

时间:2019-06-28 13:45:14

标签: c# types json.net json-serialization

我正在尝试使用Newtonsoft.Json实现自定义序列化,在这里我希望所有字段都进行序列化,然后反序列化为它们的正确类型。该类包含“对象”类型的字段,实现ISerializable,具有[Serializable]属性集以及序列化构造函数。我将此对象字段的值设置为某个类的实例,然后对其进行序列化。我使用JsonSerializer设置为TypeNameHandling的{​​{1}}。

这是我正在尝试的代码:

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

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

  1. 使用上面的代码。完全符合我上面的描述。
  2. 在我要序列化的对象上使用{ "something": { "$type": "ConsoleApp1.Program+SomeOtherClass, ConsoleApp1", "Foo": "fqwkifjwq", "Bar": "My first" } } 属性。然后,我在[JsonObject(MemberSerialization = MemberSerialization.Fields)]字段(一个Foobar.Something实例)上获得了正确类型的对象,但是,任何具有默认值的非序列化字段都将初始化为null而不是其默认值(例如字段SomeOtherClass)。
  3. 删除Foobar.someOtherObject属性。然后Serializable和序列化构造函数不会被调用。
  4. 将JsonSerializer的ISerializable GetObjectData属性设置为TypeNameHandling(无效)。

那么-关于如何解决此问题的任何提示?

1 个答案:

答案 0 :(得分:1)

因此,总结一下您的问题/评论:

  1. 您有一个庞大而复杂的旧数据结构,您想要对其进行序列化和反序列化。
  2. 您要反序列化的许多数据都在私有字段中,因此难以为它们添加属性或添加公共属性。
  3. 其中一些字段的类型为object,并且您希望保留往返的那些值的类型。
  4. 其中某些字段已明确标记为出于序列化目的而被忽略,并且它们具有您要保留的默认值。
  5. 您尝试使用TypeNameHandling.Auto并实现ISerializable;这适用于序列化(子对象类型已写入JSON),但不适用于反序列化(返回了JObject而不是实际的子对象实例)。
  6. 您尝试使用TypeNameHandling.Auto,并用MemberSerialization.Fields标记您的外部类;这适用于序列化,但是在反序列化时,您丢失了忽略字段的默认值。

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

可序列化

当Json.Net确实将序列类型的信息写入JSON时,Json.Net不尊重TypeNameHandling.Auto子对象的反序列化上的ISerializable设置似乎有些奇怪。我不知道这是错误,疏忽,还是这里存在技术限制。无论如何,它的行为均不符合预期。

但是,您也许可以实施解决方法。由于您确实在{{1}中返回了JObject,并且SerializationInfo中包含了JObject字符串,因此您可以创建一个扩展方法,该方法将从基于$type的{​​{1}}:

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; } } 使用它:

GetValue

MemberSerialization.Fields

使用object属性似乎可以满足您的所有要求,只是会丢失某些被忽略属性的默认值。事实证明,使用public Foobar(SerializationInfo info, StreamingContext context) { something = info.GetObject("something"); } 时Json.Net故意不调用普通的构造函数-而是使用FormatterServices.GetUninitializedObject方法在填充字段之前创建完全空对象从JSON。显然,这将防止默认值被初始化。

要解决此问题,您可以使用自定义[JsonObject(MemberSerialization = MemberSerialization.Fields)]来替换对象创建功能,例如:

MemberSerialization.Fields

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

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

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