我正在尝试使用Newtonsoft.Json实现自定义序列化,我希望将所有字段序列化,然后反序列化为正确的类型。该类包含一个类型为"object"的字段,实现ISerializable
,具有[Serializable]
属性集,并具有序列化构造函数。我将这个对象字段的值设置为某个类的实例,然后对其进行序列化。我使用TypeNameHandling
设置为TypeNameHandling.Auto
的JsonSerializer
。
这是我正在尝试的代码:
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
(无效)。
那么 - 关于如何解决这个问题的任何提示?
因此,总结一下您的问题/评论:
- 您有一个庞大而复杂的遗留数据结构,您希望对其进行序列化和反序列化。
- 要反序列化的大部分数据都在私有字段中,向这些字段添加属性或添加公共属性太麻烦了。
- 其中一些字段的类型为
object
,您希望为往返保留这些值的类型。 - 其中一些字段被明确标记为出于序列化目的而被忽略,并且它们具有您希望保留的默认值。
- 您尝试使用
TypeNameHandling.Auto
并实现ISerializable
;这适用于序列化(子对象类型已写入JSON),但不适用于反序列化(您得到了一个JObject
而不是实际的子对象实例)。 - 您尝试使用
TypeNameHandling.Auto
并用MemberSerialization.Fields
标记外部类;这适用于序列化,但在反序列化时,您丢失了忽略字段的默认值。
让我们看看这两种方法,看看我们能做什么。
可独立化
Json.Net 在序列化时确实将这些对象的类型信息写入 JSON 时,不尊重ISerializable
子对象的反序列化TypeNameHandling.Auto
设置,这似乎有点奇怪。 我不知道这是一个错误,一个疏忽,还是这里有一些技术限制。 无论如何,它的行为都不符合预期。
但是,您可以实施解决方法。 由于您确实在SerializationInfo
中获得了JObject
,并且该JObject
中包含$type
字符串,因此您可以创建一个扩展方法,该方法将根据$type
从JObject
创建子对象:
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
,只要类型object
:
public Foobar(SerializationInfo info, StreamingContext context)
{
something = info.GetObject("something");
}
成员序列化。字段
看起来使用[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();