2016-12-23 60 views
3

我遇到下面的問題,其中大部分是相同的,我遇到的問題來創建陣列或只讀列表,或列表:不能保留參考從非默認的構造函數

JSON.NET cannot handle simple array deserialization?

然而,我情況稍有不同。如果我從該問題修改Test類以具有相同類型的數組屬性,我會得到相同的反序列化錯誤。

class Test 
{ 
    public Test[] Tests; 
} 

var settings = new JsonSerializerSettings 
{ 
    PreserveReferencesHandling = PreserveReferencesHandling.All 
}; 

var o = new Test { Tests = new[] { new Test(), new Test() } }; 
//var o = new Test(); //this works if I leave the Tests array property null 
var arr = new[] { o, o }; 
var ser = JsonConvert.SerializeObject(arr, settings); 

arr = ((JArray)JsonConvert.DeserializeObject(ser, settings)).ToObject<Test[]>(); 

我敢打賭,我缺少的Tests財產的重要因素。

+0

我想我想通了。必須創建一個定製的Json轉換器。只要我確認修復,我會盡快回復結果 – oscilatingcretin

回答

4

Json.NET根本沒有實現保留對只讀集合和數組的引用。這是在異常消息明確指出:

Newtonsoft.Json.JsonSerializationException:無法保存參考 從非默認的構造函數 創建陣列或只讀列表,或列表:Question41293407.Test []。

之所以Newtonsoft還沒有實現,這是他們的基準跟蹤功能旨在能夠保存遞歸自引用的。因此,在讀取其內容之前,必須先分配被反序列化的對象,以便在內容反序列化期間成功解析嵌套的反向引用。但是,只有在的內容已被讀取後,只能分配只讀集合,因爲根據定義它是隻讀的。

但是,數組的特殊之處在於它們只是「半」的只讀:分配後不能調整大小,但可以更改單個條目。 (有關這方面的討論,請參閱Array.IsReadOnly inconsistent depending on interface implementation)。可以利用這一事實爲陣列創建custom JsonConverter,在讀取期間,將JSON加載到中間JToken中,通過查詢令牌的內容來分配正確大小的數組,增加陣列到serializer.ReferenceResolver,內容反序列化到一個列表,然後最後填充列表中的數組項:

public class ArrayReferencePreservngConverter : JsonConverter 
{ 
    const string refProperty = "$ref"; 
    const string idProperty = "$id"; 
    const string valuesProperty = "$values"; 

    public override bool CanConvert(Type objectType) 
    { 
     return objectType.IsArray; 
    } 

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
     if (reader.TokenType == JsonToken.Null) 
      return null; 
     else if (reader.TokenType == JsonToken.StartArray) 
     { 
      // No $ref. Deserialize as a List<T> to avoid infinite recursion and return as an array. 
      var elementType = objectType.GetElementType(); 
      var listType = typeof(List<>).MakeGenericType(elementType); 
      var list = (IList)serializer.Deserialize(reader, listType); 
      if (list == null) 
       return null; 
      var array = Array.CreateInstance(elementType, list.Count); 
      list.CopyTo(array, 0); 
      return array; 
     } 
     else 
     { 
      var obj = JObject.Load(reader); 
      var refId = (string)obj[refProperty]; 
      if (refId != null) 
      { 
       var reference = serializer.ReferenceResolver.ResolveReference(serializer, refId); 
       if (reference != null) 
        return reference; 
      } 
      var values = obj[valuesProperty]; 
      if (values == null || values.Type == JTokenType.Null) 
       return null; 
      if (!(values is JArray)) 
      { 
       throw new JsonSerializationException(string.Format("{0} was not an array", values)); 
      } 
      var count = ((JArray)values).Count; 

      var elementType = objectType.GetElementType(); 
      var array = Array.CreateInstance(elementType, count); 

      var objId = (string)obj[idProperty]; 
      if (objId != null) 
      { 
       // Add the empty array into the reference table BEFORE poppulating it, 
       // to handle recursive references. 
       serializer.ReferenceResolver.AddReference(serializer, objId, array); 
      } 

      var listType = typeof(List<>).MakeGenericType(elementType); 
      using (var subReader = values.CreateReader()) 
      { 
       var list = (IList)serializer.Deserialize(subReader, listType); 
       list.CopyTo(array, 0); 
      } 

      return array; 
     } 
    } 

    public override bool CanWrite { get { return false; } } 

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
    { 
     throw new NotImplementedException(); 
    } 
} 

這種方法的內存效率不是很大,所以對於大集合這將是更好的切換到List<T>

然後使用它像:

var settings = new JsonSerializerSettings 
{ 
    Converters = { new ArrayReferencePreservngConverter() }, 
    PreserveReferencesHandling = PreserveReferencesHandling.All 
}; 
var a2 = JsonConvert.DeserializeObject<Test[]>(jsonString, settings); 

注意轉換器是完全通用的,適用於所有的數組。

示例fiddle顯示嵌套遞歸自引用的成功反序列化。

+1

感謝您的回答。它最終只是使用'PreserveReferencesHandling.Objects',因爲我意識到我真的不需要對數組本身的引用,而只需要引用索引中的元素。我改變它後,它工作得很好。我將繼續並在此處標記爲答案 – oscilatingcretin

+0

這是唯一的問題,即將數組放入對象定義(編譯時)對象中時。那麼objectType.IsArray不會是真的,轉換器將不會被執行。 – Arash

0

我覺得這個代碼是好的,但需要細化

var elementType = objectType.IsArray ? objectType.GetElementType() : objectType.GetGenericArguments()[0]; 

objectType.IsGenericType可能是真實的話,我們需要使用GetGenericArguments()[0]