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的实例作为对该类中静态成员的引用。

JSON.Net保留对静态对象的引用

您正在寻找的是对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类型以实现ISerializableIObjectReference:

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。下面是相关的示例,您可以在其中引用列表中的相同对象并指向它。我不认为它实际上保留了旧的参考,但当你在列表中有相同的东西并且你不想使用自己的IEqualityComparerIEquatable实现时,它肯定会有所帮助:

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线程