2016-09-25 78 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接口。