c# - JSON.Net no respeta las PreserveReferencesHandling on Deserialization
(1)
Aquí está lo que probé y trabajé bien:
Las clases
public class TemplateDataLinkedListBase<T> where T : TemplateDataLinkedListBase<T>
{
[JsonProperty(TypeNameHandling = TypeNameHandling.Objects)]
public T Parent { get; set; }
[JsonProperty(TypeNameHandling = TypeNameHandling.Objects)]
public List<T> Children { get; set; }
}
public class TemplateDataQueryElement : TemplateDataLinkedListBase<TemplateDataQueryElement>
{
public string Query { get; set; }
public TemplateDataQueryElement()
{
Children = new List<TemplateDataQueryElement>();
}
}
Inicialización
var childLowest = new TemplateDataQueryElement
{
Query = "Lowest"
};
var childMiddle = new TemplateDataQueryElement
{
Query = "Middle",
Children = new List<TemplateDataQueryElement>
{
childLowest
}
};
childLowest.Parent = childMiddle;
var parent = new TemplateDataQueryElement
{
Query = "Parent",
Children = new List<TemplateDataQueryElement>
{
childMiddle
}
};
childMiddle.Parent = parent;
Ajustes de serialización
var _jsonSettings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Auto,
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
ObjectCreationHandling = ObjectCreationHandling.Auto
};
Publicación por entregas
var serializedStr = JsonConvert.SerializeObject(parent, Formatting.Indented, _jsonSettings);
El json serializado se ve así:
{
"$id": "1",
"Query": "Parent",
"Parent": null,
"Children": [
{
"$id": "2",
"Query": "Middle",
"Parent": {
"$ref": "1"
},
"Children": [
{
"$id": "3",
"Query": "Lowest",
"Parent": {
"$ref": "2"
},
"Children": []
}
]
}
]
}
Deserialización
var deserializedStructure = JsonConvert.DeserializeObject<TemplateDataQueryElement>(serializedStr, _jsonSettings);
Las referencias en la deserializedStructure
se conservan correctamente.
Demostración https://dotnetfiddle.net/j1Qhu6
ACTUALIZACIÓN 1
La razón por la que funciona mi ejemplo y el código que publicaste en los enlaces adicionales no es porque mis clases contengan el constructor predeterminado y el tuyo no. Al analizar sus clases, agregarles un constructor predeterminado, no interrumpirá la funcionalidad y la deserialización tendrá éxito con la propiedad principal inicializada correctamente. Entonces, lo que básicamente necesitas hacer es agregar un constructor predeterminado a ambas clases:
public class TemplateDataLinkedListBase<T> where T : TemplateDataLinkedListBase<T>
{
[JsonProperty(TypeNameHandling = TypeNameHandling.Objects)]
public T Parent { get; set; }
[JsonProperty(TypeNameHandling=TypeNameHandling.Objects)]
public List<T> Children { get; set; }
public string EntityName { get; set; }
public HashSet<string> Fields { get; set; }
public string Key { get { return getKey(); } }
public TemplateDataLinkedListBase()
{
Children = new List<T>();
Fields = new HashSet<string>();
}
public TemplateDataLinkedListBase(string entityName)
{
EntityName = entityName;
Children = new List<T>();
Fields = new HashSet<string>();
}
private string getKey()
{
List<string> keys = new List<string>();
keys.Add(this.EntityName);
getParentKeys(ref keys, this);
keys.Reverse();
return string.Join(".", keys);
}
private void getParentKeys(ref List<string> keys, TemplateDataLinkedListBase<T> element)
{
if (element.Parent != null)
{
keys.Add(element.Parent.EntityName);
getParentKeys(ref keys, element.Parent);
}
}
public T AddChild(T child)
{
child.Parent = (T)this;
Children.Add(child);
return (T)this;
}
public T AddChildren(List<T> children)
{
foreach (var child in children)
{
child.Parent = (T)this;
}
Children.AddRange(children);
return (T)this;
}
public void AddFields(IEnumerable<string> fields)
{
foreach (var field in fields)
this.Fields.Add(field);
}
public TemplateDataLinkedListBase<T> Find(string searchkey)
{
if (this.Key == searchkey)
{
return this;
}
else
{
foreach (var child in Children)
{
if (child.Key == searchkey)
{
return child;
}
else
{
var childResult = child.Find(searchkey);
if (childResult != null) return childResult;
}
}
}
return null;
}
}
public class TemplateDataQueryElement : TemplateDataLinkedListBase<TemplateDataQueryElement>, ITemplateDataQueryElement
{
public string TemplateModelName { get; set; }
public string RecordId { get; set; }
public string ParentForeignKeyName { get; set; }
public string Query { get; set; }
public dynamic ObjectData { get; set; }
public ITemplateDataParseResult ParseResult { get; set; }
public TemplateDataQueryElement() : base()
{
Fields.Add("Id"); //Always retrieve Id''s
ObjectData = new ExpandoObject();
}
public TemplateDataQueryElement(string entityName)
: base(entityName)
{
Fields.Add("Id"); //Always retrieve Id''s
ObjectData = new ExpandoObject();
}
public override string ToString()
{
return string.Format("{0}: {1}", EntityName, Query);
}
}
La propiedad EntityName
que estableció a través de su constructor se deserializará correctamente, ya que es una propiedad pública.
He vinculado doblemente la lista que estoy tratando de deserializar.
Mi escenario se relaciona estrechamente con este SO: Lista doblemente vinculada a JSON
Tengo la siguiente configuración de JSON:
_jsonSettings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Auto,
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
ObjectCreationHandling = ObjectCreationHandling.Auto
};
Cuando veo la salida serializada, parece correcta, y las referencias entre los nodos están representadas correctamente.
Cuando se deserializan los datos, las propiedades principales en los objetos secundarios son nulas, aunque se rellenan con $ ref correctamente.
A continuación se muestra una muestra del JSON (recortado para facilitar la lectura)
En el proceso de escribir esta pregunta, es posible que haya visto la fuente del problema ...
Los objetos en la propiedad de matriz "Hijos" no tienen atributos de tipo $.
Esto podría deberse a que las propiedades Hijos y Padres son de tipo genérico T.
Tenga en cuenta que el tipo real que se serializa es una clase derivada de TemplateDataLinkedListBase
public class TemplateDataQueryElement : TemplateDataLinkedListBase<TemplateDataQueryElement>
Aquí hay un extracto de la clase base:
public class TemplateDataLinkedListBase<T> where T : TemplateDataLinkedListBase<T>
{
[JsonProperty(TypeNameHandling = TypeNameHandling.Objects)]
public T Parent { get; set; }
[JsonProperty(TypeNameHandling=TypeNameHandling.Objects)]
public List<T> Children { get; set; }
}
¿Cómo puedo deserializar este JSON de tal manera que la propiedad principal no sea nula y contenga una referencia al objeto principal?
{
"$id": "9",
"$type": "Contracts.Models.TemplateDataQueryElement, Contracts",
"Query": null,
"Parent": null,
"Children": [
{
"$id": "11",
"Query": null,
"Parent": {
"$ref": "9"
},
"Children": [
{
"$id": "13",
"Query": null,
"Parent": {
"$ref": "11"
},
"Children": [],
"EntityName": "Widgets",
"Fields": [
"Id"
],
"Key": ""
},
Aquí están los enlaces de PasteBin al código relevante:
http://pastebin.com/i1jxVGG3 http://pastebin.com/T1xqEWW2 http://pastebin.com/ha42SeF7 http://pastebin.com/cezwZqx6 http://pastebin.com/uFbTbUZe http://pastebin.com/sRhNQgzh