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"
}
}
这是我到目前为止尝试过的方法:
- 使用上面的代码。完全按照我上面的描述去做。
- 在我正在序列化的对象上使用
[JsonObject(MemberSerialization = MemberSerialization.Fields)]
属性。然后我在 Foobar.Something
字段(一个 SomeOtherClass
实例)上得到一个正确类型的对象,但是,任何具有默认值的非序列化字段都被初始化为 null 而不是它们的默认值(比如字段 Foobar.someOtherObject
).
- 正在删除
Serializable
属性。然后 ISerializable GetObjectData
和序列化构造函数不被调用。
- 将 JsonSerializer 的
TypeNameHandling
属性 设置为 All
(无效)。
所以 - 关于如何解决这个问题的任何提示?
所以,总结一下你的 question/comments:
- 您有一个庞大而复杂的遗留数据结构,您希望对其进行序列化和反序列化。
- 您想要 de/serialize 的大部分数据都在私有字段中,向这些字段添加属性或添加 public 属性太麻烦了。
- 其中一些字段的类型为
object
,您希望为往返保留这些值的类型。
- 为了序列化目的,其中一些字段被明确标记为忽略,并且它们具有您希望保留的默认值。
- 您尝试使用
TypeNameHandling.Auto
并实施 ISerializable
;这适用于序列化(子对象类型被写入 JSON)但不适用于反序列化(你得到一个 JObject
返回而不是实际的子对象实例)。
- 您尝试使用
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();
我正在尝试使用 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"
}
}
这是我到目前为止尝试过的方法:
- 使用上面的代码。完全按照我上面的描述去做。
- 在我正在序列化的对象上使用
[JsonObject(MemberSerialization = MemberSerialization.Fields)]
属性。然后我在Foobar.Something
字段(一个SomeOtherClass
实例)上得到一个正确类型的对象,但是,任何具有默认值的非序列化字段都被初始化为 null 而不是它们的默认值(比如字段Foobar.someOtherObject
). - 正在删除
Serializable
属性。然后ISerializable GetObjectData
和序列化构造函数不被调用。 - 将 JsonSerializer 的
TypeNameHandling
属性 设置为All
(无效)。
所以 - 关于如何解决这个问题的任何提示?
所以,总结一下你的 question/comments:
- 您有一个庞大而复杂的遗留数据结构,您希望对其进行序列化和反序列化。
- 您想要 de/serialize 的大部分数据都在私有字段中,向这些字段添加属性或添加 public 属性太麻烦了。
- 其中一些字段的类型为
object
,您希望为往返保留这些值的类型。 - 为了序列化目的,其中一些字段被明确标记为忽略,并且它们具有您希望保留的默认值。
- 您尝试使用
TypeNameHandling.Auto
并实施ISerializable
;这适用于序列化(子对象类型被写入 JSON)但不适用于反序列化(你得到一个JObject
返回而不是实际的子对象实例)。 - 您尝试使用
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();