I have doubly linked list that I am trying to deserialise.
My scenario closely relates to this SO: Doubly Linked List to JSON
I have the following JSON settings:
_jsonSettings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Auto,
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
ObjectCreationHandling = ObjectCreationHandling.Auto
};
When I look at the serialised output, it appears correct, and the references between nodes are properly represented.
When the data is deserialised, the Parent properties in the Child objects are null, even though they are populated with $ref correctly.
Below is a sample of the JSON (trimmed for readability)
In the process of typing this question - I may have seen the source of the trouble...
The objects in the "Children" array property do not have $type attributes.
This could be because the Children and Parent properties are of generic type T.
Note that the actual type being serialised is a derived class of TemplateDataLinkedListBase
public class TemplateDataQueryElement : TemplateDataLinkedListBase<TemplateDataQueryElement>
Here is an excerpt of the base class:
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; }
}
How can I deserialise this JSON in such a way that the Parent property is not null and contains a reference to the parent object?
{
"$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": ""
},
Here are PasteBin links to the relevant code:
http://pastebin.com/i1jxVGG3 http://pastebin.com/T1xqEWW2 http://pastebin.com/ha42SeF7 http://pastebin.com/cezwZqx6 http://pastebin.com/uFbTbUZe http://pastebin.com/sRhNQgzh
Here is what I tried and worked fine:
The classes
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>();
}
}
Initialization
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;
Serialization settings
var _jsonSettings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Auto,
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
ObjectCreationHandling = ObjectCreationHandling.Auto
};
Serialization
var serializedStr = JsonConvert.SerializeObject(parent, Formatting.Indented, _jsonSettings);
The serialized json looks like this:
{
"$id": "1",
"Query": "Parent",
"Parent": null,
"Children": [
{
"$id": "2",
"Query": "Middle",
"Parent": {
"$ref": "1"
},
"Children": [
{
"$id": "3",
"Query": "Lowest",
"Parent": {
"$ref": "2"
},
"Children": []
}
]
}
]
}
Deserialization
var deserializedStructure = JsonConvert.DeserializeObject<TemplateDataQueryElement>(serializedStr, _jsonSettings);
The references in the deserializedStructure
are preserved correctly.
Demo https://dotnetfiddle.net/j1Qhu6
UPDATE 1
The reason my example works, and the code you posted in the additional links doesn't is because my classes contain default constructor, and yours don't. Analyzing your classes, adding a default constructor to them, it won't break the functionality and the deserialization will be successful with Parent
property initialized correctly. So what you basically need to do is add a default constructor to both classes:
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);
}
}
The EntityName
property which you set through your constructor, will be deserialized correctly, since it is a public property.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With