Dot-Net

無法保留對數組或只讀列表或從非預設建構子創建的列表的引用

  • February 12, 2021

我遇到了以下問題,該問題與我遇到的問題基本相同:

JSON.NET 不能處理簡單的數組反序列化?

但是,我的情況略有不同。如果我修改該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

Json.NET 根本沒有實現為只讀集合和數組保留引用。這在異常消息中明確說明:

Newtonsoft.Json.JsonSerializationException:無法保留對數組或只讀列表或從非預設建構子創建的列表的引用:Question41293407.Test

$$ $$.

Newtonsoft 沒有實現這一點的原因是他們的引用跟踪功能旨在能夠保留遞歸自引用。因此,必須在讀取其內容之前分配被反序列化的對象,以便在內容反序列化期間可以成功解決嵌套的反向引用。但是,只讀集合只能在其內容被讀取才能分配,因為根據定義它是只讀的。

然而,數組的特殊之處在於它們只是“半”只讀的:它們在分配後無法調整大小,但是可以更改單個條目。(有關此問題的討論,請參閱Array.IsReadOnly 不一致,具體取決於介面實現。)可以利用這一事實為數組創建自定義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);

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

範例小提琴顯示嵌套遞歸自引用的成功反序列化。

引用自:https://stackoverflow.com/questions/41293407