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

时间:2016-09-25 21:26:24

标签: c# json json.net

我正在使用类型对象模式的变体(基本上是智能枚举)。由于这个问题最好用代码解释,我会直接进入它。

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

4 个答案:

答案 0 :(得分:1)

您正在寻找的是对IObjectReference界面的支持:

  

在对不同对象的引用的对象上实现此接口,在完全还原当前对象之前无法解析该对象。在修复阶段,任何实现IObjectReference的对象都会查询其真实对象,并将该对象插入到图形中。

不幸的是,Json.NET不支持此接口。但是,在有问题的类型也实现ISerializable 的情况下,扩展Json.NET以支持此接口非常容易。这是一个非常合理的限制,因为在实践中,这两个接口通常一起使用,如documentation example中所示。

首先,介绍以下custom contract resolver

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接口。

答案 1 :(得分:0)

默认情况下不可行,因为JSON反序列化器不关心类中的现有引用或静态对象。

您可以使用自定义Equals方法比较相等性,但我想这并不是您想要的。

答案 2 :(得分:0)

不要序列化MyObj.Test,使用Ignore属性来抑制它。而是公开一个返回MyObj.Test.ID的属性MyObj.TestID。在MyObj上设置TestID时,从ID键控的静态集合加载Test,并将MyObj.Test设置为该值。

答案 3 :(得分:0)

首先,当您不希望每次定义基类的新派生时都不想通过继承层次结构时,应该使用Type Object模式。将类型对象附加为static并不能让人感到自豪。正如你所提到的那样,我不会跳过它。

看起来你希望能够在使用json.net进行反序列化后保留引用。

现在,如果您想这样做,可能需要查看here

从上述链接中获取片段,因为这里有一个示例,因为这是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定义here

此外,如果您想拥有类似的东西,请查看Json.net的源代码here

您可以使用IdReferenceResolver将对象引用保存为Guid,并可能按照您的方式使用它。

如果您想了解DefaultReferenceResolver的工作原理,可以查看此stackoverflow thread