2016-09-25 80 views
1

我正在使用类型对象模式(基本上是智能枚举)的变体。由于这个问题最好用代码解释,我会直接跳到它。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); 
    } 

在本例中的测试是类型对象,它的实例被分配给它的静态字段,B.在现实生活中的场景将有多个这些静态字段,每个表示不同的类型。当我序列化和反序列化时,测试对象被纯粹作为数据序列化。我明白为什么会发生这种情况,但我不知道该怎么做。我想以某种方式保留Test的实例是对该类中的静态成员的引用。

回答

0

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

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

0

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

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

0

你所寻找的是为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; 
    } 
} 

现在,修改你的伪枚举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接口。