JSON.Net保留对静态对象的引用
本文关键字:对象 引用 静态 Net 保留 JSON | 更新日期: 2023-09-27 18:08:40
我正在使用类型对象模式的一种变体(基本上是智能枚举)。既然这个问题可以用代码来最好地解释,我就直接开始吧。
class Program
{
static void Main(string[] args)
{
Test C = Test.B;
Console.WriteLine(C == Test.B); //Returns true
string Json = JsonConvert.SerializeObject(C);
C = JsonConvert.DeserializeObject<Test>(Json);
Console.WriteLine(C == Test.B); //Returns false
}
}
public class Test
{
public int A { get; set; }
public Test(int A)
{
this.A = A;
}
public static Test B = new Test(100);
}
在这个例子中Test是类型对象,它的实例被分配给它的静态字段b。在实际场景中,会有多个这样的静态字段,每个字段代表一个不同的类型。当我序列化和反序列化时,测试对象被纯粹序列化为数据。我明白为什么会这样,但我不知道该怎么做。我想以某种方式保留Test的实例作为对该类中静态成员的引用。
您正在寻找的是对IObjectReference
接口的支持:
在引用不同对象的对象上实现此接口,该对象在当前对象完全恢复之前无法解析。在固定阶段,任何实现IObjectReference的对象都将被查询其实际对象,并将该对象插入到图中。
不幸的是,Json。NET不支持此接口。然而,事实证明扩展Json非常容易。在讨论的类型也实现ISerializable
的情况下,NET支持该接口。这是一个相当合理的限制,因为在实践中,这两个接口经常一起使用,如文档示例中所示。
public class ISerializableRealObjectContractResolver : DefaultContractResolver
{
// As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
// http://www.newtonsoft.com/json/help/html/ContractResolver.htm
// http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
// "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
static ISerializableRealObjectContractResolver instance;
static ISerializableRealObjectContractResolver() { instance = new ISerializableRealObjectContractResolver(); }
public static ISerializableRealObjectContractResolver Instance { get { return instance; } }
public ISerializableRealObjectContractResolver()
: base()
{
this.IgnoreSerializableInterface = false;
}
protected override JsonISerializableContract CreateISerializableContract(Type objectType)
{
var contract = base.CreateISerializableContract(objectType);
var constructor = contract.ISerializableCreator;
contract.ISerializableCreator = args =>
{
var obj = constructor(args);
if (obj is IObjectReference)
{
var context = (StreamingContext)args[1];
obj = ((IObjectReference)obj).GetRealObject(context);
}
return obj;
};
return contract;
}
}
现在,修改您的psuedo-enum Test
类型以实现ISerializable
和IObjectReference
:
public class Test : ISerializable, IObjectReference
{
readonly int a;
public int A { get { return a; } }
public Test(int A)
{
this.a = A;
}
public static readonly Test B = new Test(100);
#region ISerializable Members
protected Test(SerializationInfo info, StreamingContext context)
{
a = info.GetInt32("A");
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("A", A);
}
#endregion
#region IObjectReference Members
public object GetRealObject(StreamingContext context)
{
// Check all static properties to see whether the key value "A" matches. If so, return the static instance.
if (this.A == B.A)
return B;
return this;
}
#endregion
}
我还使类型不可变,因为这显然是这里的要求。
现在你的单元测试将通过使用这个契约解析器:
Test C = Test.B;
Console.WriteLine(C == Test.B); //Returns true
string Json = JsonConvert.SerializeObject(C, new JsonSerializerSettings { ContractResolver = ISerializableRealObjectContractResolver.Instance });
Console.WriteLine(Json);
C = JsonConvert.DeserializeObject<Test>(Json, new JsonSerializerSettings { ContractResolver = ISerializableRealObjectContractResolver.Instance });
Console.WriteLine(C == Test.B); //Still returns true
if (!object.ReferenceEquals(C, Test.B))
{
throw new InvalidOperationException("!object.ReferenceEquals(C, Test.B)");
}
else
{
Console.WriteLine("Global singleton instance deserialized successfully.");
}
注意Json。. NET只支持完全信任的ISerializable
接口
默认情况下不可能,因为JSON反序列化器不关心类中的现有引用或静态对象。
您可以使用自定义Equals
方法比较相等性,但我猜这不是您想要的。
不要序列化MyObj。测试,用Ignore属性来抑制它。相反,公开属性MyObj。返回MyObj.Test.ID的testd。当testd在MyObj上设置时,从一个由ID键接的静态集合中加载测试,并设置MyObj。测试该值
首先,当您不想每次定义基类的新派生时都要遍历继承层次结构时,应该使用Type Object
模式。老实说,将类型对象附加为static
在一开始就没有意义。正如你所提到的,这是一种变化,我不打算赘述。
看起来你希望即使在使用json.net反序列化之后也能保留引用。
如果你想这么做你可能想看一下这里
从前面提到的链接中提取片段,因为这里最好有一个示例,因为这是StackOverflow的答案。即使所提供的链接已断开,它也应该维持。
您的第一个选项是使用默认的PreserveReferencesHandling
。下面是相关的示例,您可以在其中引用列表中的相同对象并指向它。我不认为它实际上保留了旧的参考,但当你在列表中有相同的东西并且你不想使用自己的IEqualityComparer
或IEquatable
实现时,它肯定会有所帮助:
string json = JsonConvert.SerializeObject(people, Formatting.Indented,
new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects });
//[
// {
// "$id": "1",
// "Name": "James",
// "BirthDate": "1983-03-08T00:00Z",
// "LastModified": "2012-03-21T05:40Z"
// },
// {
// "$ref": "1"
// }
//]
List<Person> deserializedPeople = JsonConvert.DeserializeObject<List<Person>>(json,
new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects });
Console.WriteLine(deserializedPeople.Count);
// 2
Person p1 = deserializedPeople[0];
Person p2 = deserializedPeople[1];
Console.WriteLine(p1.Name);
// James
Console.WriteLine(p2.Name);
// James
bool equal = Object.ReferenceEquals(p1, p2);
// true
您可以使用IsReference
属性来控制哪些属性将被保留为引用:
[JsonObject(IsReference = true)]
public class EmployeeReference
{
public string Name { get; set; }
public EmployeeReference Manager { get; set; }
}
现在,如果你想在代码中为自己保留完全相同的引用(我不认为这是一个很好的设计,你可能只需要一个Equality
比较方法,并完成它),你需要在这里定义一个自定义的IReferenceResolver
。
此外,如果您想要这样的东西,只需查看此处的Json.net源代码即可。
这是一个IdReferenceResolver
,你可以用它来保存你的对象引用作为Guid,并可能使用它你的方式。
如果你想知道DefaultReferenceResolver
是如何工作的,你可以看看这个stackoverflow线程