.net - que - No se puede conservar la referencia a la lista de arreglos o de solo lectura, o la lista creada a partir de un constructor no predeterminado
vector dinamico c++ new (2)
Json.NET simplemente no ha implementado la preservación de referencias para colecciones y arreglos de solo lectura. Esto se establece explícitamente en el mensaje de excepción:
Newtonsoft.Json.JsonSerializationException: no se puede conservar la referencia a la matriz o lista de solo lectura, o la lista creada a partir de un constructor no predeterminado: Question41293407.Test [].
La razón por la que Newtonsoft no ha implementado esto es porque su funcionalidad de rastreo de referencia está destinada a ser capaz de preservar auto referencias recursivas . Por lo tanto, el objeto que se está deserializando debe asignarse antes de leer su contenido, de modo que las referencias anidadas anidadas puedan resolverse satisfactoriamente durante la deserialización del contenido. Sin embargo, una colección de solo lectura solo se puede asignar después de que se haya leído su contenido, ya que, por definición, es de solo lectura.
Sin embargo, las matrices son peculiares porque solo son "semi" de solo lectura: no se pueden cambiar de tamaño después de haberlas asignado, sin embargo, las entradas individuales se pueden cambiar. (Consulte Array.IsReadOnly inconsistent dependiendo de la implementación de la interfaz para una discusión sobre esto.) Es posible aprovechar este hecho para crear un JsonConverter
personalizado para matrices que, durante la lectura, carga el JSON en un JToken
intermedio, asigna una matriz del el tamaño correcto al consultar los contenidos del token, agrega el conjunto al serializer.ReferenceResolver
, deserializa el contenido en una lista y finalmente rellena las entradas del conjunto de la lista:
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();
}
}
La eficiencia de la memoria de este enfoque no es grande, por lo que para colecciones grandes sería mejor cambiar a una List<T>
.
Entonces úsalo como:
var settings = new JsonSerializerSettings
{
Converters = { new ArrayReferencePreservngConverter() },
PreserveReferencesHandling = PreserveReferencesHandling.All
};
var a2 = JsonConvert.DeserializeObject<Test[]>(jsonString, settings);
Tenga en cuenta que el convertidor es completamente genérico y funciona para todas las matrices.
Violín de muestra que muestra la deserialización exitosa de autorreferencias recursivas anidadas.
Me encontré con la siguiente pregunta, que es en su mayoría idéntica al problema que estoy teniendo:
JSON.NET no puede manejar la deserialización de matriz simple?
Sin embargo, mi situación es ligeramente diferente. Si modifico la clase Test
de esa pregunta para tener una propiedad de matriz del mismo tipo, obtengo el mismo error de deserialización.
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[]>();
Apuesto a que me falta un atributo importante en la propiedad Tests
.
Creo que este código es bueno, pero necesita un refinamiento
var elementType = objectType.IsArray ? objectType.GetElementType() : objectType.GetGenericArguments()[0];
objectType.IsGenericType
podría ser cierto, entonces necesitamos usar GetGenericArguments()[0]