JSON .Net not respecting PreserveReferencesHandling on Deserialization - c#

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.

Related

How to recursively traverse down a nested object with n levels and refer back to any grandparent level objects on kth level, and change those objects

I need to recurse down an object with arbitrarily deep nesting of child objects. On each level of nesting I reach, I need to refer back to any (potentially all) of the objects on levels I've already been through, i.e. any grandparents of the current child object I am at. I then need to set properties on any of those referenced parent objects based on the current object's state. The properties I'm setting is determined by JSON.
Some values will be determined at runtime (those that are randomly generated in the example below) and others will just be strings from the JSON.
Minimum reproducible example:
Take this JSON as input:
{
"name": "foo",
"targetChildren": [
"baz",
"quux"
],
"valsFromChildren": [],
"runtimeValsFromChildren": [],
"childObjects": [
{
"name": "baz",
"valToGive": "giftFromBaz",
"targetChildren": [
"qux"
],
"valsFromChildren": [],
"runtimeValsFromChildren": [],
"childObjects": [
{
"name": "qux",
"valToGive": "giftFromQux"
},
{
"name": "quux",
"valToGive": "giftFromQuux"
}
]
}
]
}
The targetChildren refer to the names of the child/grandchild objects from which I want to get values and give them to "this" object. The valsFromChildren List should be populated based on that connection - the children with the matching names from targetChildren should put their valToGive value in the valsFromChildren List on the object targeting them. We can assume that there will be no duplicate names.
runtimeValsFromChildren is a List that gets filled with random numbers that are computed by the child object that is giving the value to the parent.
C# console app (needs to be Dotnet version 6):
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Collections;
var json = "{\"name\":\"foo\",\"targetChildren\":[\"baz\",\"quux\"],\"valsFromChildren\":[],\"runtimeValsFromChildren\":[],\"childObjects\":[{\"name\":\"baz\",\"valToGive\":\"giftFromBaz\",\"targetChildren\":[\"qux\"],\"valsFromChildren\":[],\"runtimeValsFromChildren\":[],\"childObjects\":[{\"name\":\"qux\",\"valToGive\":\"giftFromQux\"},{\"name\":\"quux\",\"valToGive\":\"giftFromQuux\"}]}]}";
var obj = JsonSerializer.Deserialize<Obj>(json);
DoRecursion(obj);
var newObjJson = JsonSerializer.Serialize(obj);
static Obj DoRecursion(Obj obj)
{
if (obj.ChildObjects == null || obj.ChildObjects.Count <= 0)
return obj;
foreach (var child in obj.ChildObjects)
{
var parent = obj;
if (parent.TargetChildren != null && parent.TargetChildren.Contains(child.Name))
{
// Give the values to the parent that is targeting them.
parent.ValsFromChildren.Add(child.ValToGive);
parent.RuntimeValsFromChildren.Add(child.RuntimeVal);
}
return DoRecursion(child);
}
return obj;
}
class Obj
{
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("valToGive")]
public string ValToGive { get; set; }
[JsonPropertyName("targetChildren")]
public List<string> TargetChildren { get; set; }
[JsonPropertyName("valsFromChildren")]
public List<string> ValsFromChildren { get; set; }
[JsonPropertyName("runtimeValsFromChildren")]
public List<int> RuntimeValsFromChildren { get; set; }
[JsonPropertyName("childObjects")]
public List<Obj> ChildObjects { get; set; }
[JsonIgnore]
public int RuntimeVal => new Random().Next(0, 100);
}
Desired output in JSON:
{
"name": "foo",
"targetChildren": [
"baz",
"quux"
],
"valsFromChildren": [
"giftFromBaz",
"giftFromQuux"
],
"runtimeValsFromChildren": [
31,
88
],
"childObjects": [
{
"name": "baz",
"valToGive": "giftFromBaz",
"targetChildren": [
"qux"
],
"valsFromChildren": [
"giftFromQux"
],
"runtimeValsFromChildren": [
43
],
"childObjects": [
{
"name": "qux",
"valToGive": "giftFromQux"
},
{
"name": "quux",
"valToGive": "giftFromQuux"
}
]
}
]
}
Actual output:
{
"name": "foo",
"targetChildren": [
"baz",
"quux"
],
"valsFromChildren": [
"giftFromBaz"
],
"runtimeValsFromChildren": [
43
],
"childObjects": [
{
"name": "baz",
"valToGive": "giftFromBaz",
"targetChildren": [
"qux"
],
"valsFromChildren": [
"giftFromQux"
],
"runtimeValsFromChildren": [
60
],
"childObjects": [
{
"name": "qux",
"valToGive": "giftFromQux"
},
{
"name": "quux",
"valToGive": "giftFromQuux"
}
]
}
]
}
I need the valsFromChildren List on the object with name foo to include giftFromQuux (a grand child that I want to grab from the 3rd level down). At the moment it only manages to get the value from its immediate child ("baz"), not its grand child ("quux"). It needs to be a recursive solution that should work for grandparents n levels down the nesting.
I would also like to know how to not mutate the original object but instead return a copy at a different memory address, i.e. have the method not have side effects.
Thanks.
Okay so here is your c# class (I used this website)
public class ChildObject
{
public string name { get; set; }
public string valToGive { get; set; }
public List<string> targetChildren { get; set; }
public List<string> valsFromChildren { get; set; }
public List<int> runtimeValsFromChildren { get; set; }
public List<ChildObject> childObjects { get; set; }
}
public class Root
{
public string name { get; set; }
public List<string> targetChildren { get; set; }
public List<string> valsFromChildren { get; set; }
public List<int> runtimeValsFromChildren { get; set; }
public List<ChildObject> childObjects { get; set; }
}
So you just have to do :
Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(theJsonString);
And then when you finish modifying thing do:
string myNewJson = JsonConvert.SerializeObject(myDeserializedClass);
Frankly it is not totally clear on your intent/desired result here SO here is a console app that walks the deserialized and then the altered object a few ways. Perhaps you can build from this a bit. This is a bit "chatty" to give you some ideas where you can go with this.
Here is a working copy of this sample: https://dotnetfiddle.net/qakv6n
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
Console.WriteLine("Hello World");
var json = "{\"name\":\"foo\",\"targetChildren\":[\"baz\",\"quux\"],\"valsFromChildren\":[],\"runtimeValsFromChildren\":[],\"childObjects\":[{\"name\":\"baz\",\"valToGive\":\"giftFromBaz\",\"targetChildren\":[\"qux\"],\"valsFromChildren\":[],\"runtimeValsFromChildren\":[],\"childObjects\":[{\"name\":\"qux\",\"valToGive\":\"giftFromQux\"},{\"name\":\"quux\",\"valToGive\":\"giftFromQuux\"}]}]}";
var obj = JsonSerializer.Deserialize<JsonObj>(json);
Console.WriteLine($#"Hello World {obj.Name} has {obj.TargetChildren.Count} targets");
foreach (var item in obj.TargetChildren.Select((value, i) => new { i, value }))
{
Console.WriteLine($#"Hello item: {item} has targets");
var value = item.value;
var index = item.i;
Console.WriteLine($#"x: index:{index} val: {value} ");
}
DoRecursion(obj);
var newObjJson = JsonSerializer.Serialize(obj);
Console.WriteLine($#"serial {newObjJson} ");
foreach (var rv in obj.RuntimeValsFromChildren)
{
Console.WriteLine($#"Count {rv} ");
}
foreach (var v in obj.ValsFromChildren)
{
Console.WriteLine($#"Vals {v} ");
}
}
static JsonObj DoRecursion(JsonObj obj)
{
if (obj.ChildObjects == null || obj.ChildObjects.Count <= 0)
return obj;
foreach (var child in obj.ChildObjects)
{
Console.WriteLine($#"Hello Recursing Name:{child.Name} Count: {obj.ChildObjects.Count} kiddos");
var g = obj.ChildObjects.Where(co => co.Name == child.Name)
.Select(x => new{Name= x.Name, Val= x.ValToGive}).First();
Console.WriteLine($"g:{g.Name} v:{g.Val}");
var newObjJson = JsonSerializer.Serialize(obj);
var parent = obj;
if (parent.TargetChildren != null && parent.TargetChildren.Contains(child.Name))
{
// Give the values to the parent that is targeting them.
parent.ValsFromChildren.Add(child.ValToGive);
parent.RuntimeValsFromChildren.Add(child.RuntimeVal);
}
return DoRecursion(child);
}
return obj;
}
class JsonObj
{
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("valToGive")]
public string ValToGive { get; set; }
[JsonPropertyName("targetChildren")]
public List<string> TargetChildren { get; set; }
[JsonPropertyName("valsFromChildren")]
public List<string> ValsFromChildren { get; set; }
[JsonPropertyName("runtimeValsFromChildren")]
public List<int> RuntimeValsFromChildren { get; set; }
[JsonPropertyName("childObjects")]
public List<JsonObj> ChildObjects { get; set; }
[JsonIgnore]
public int RuntimeVal => new Random().Next(0, 100);
}
}

How do I create a List or Array of different data types from C# to be saved to JSON in Unity

I would like my output JSON to contain a simple array shown below
{
"attributes":[
{
"trait_type": "Background",
"value": "Green"
},
{
"trait_type": "Body",
"value": "Body_1"
},
{
"trait_type": "Outfit",
"value": "Beach_Singlet"
},
{
"display_type":"date",
"trait_type":"birthday",
"value":869270400
}
]
}
Notice how the last item is different from the previous items in the array. The variable named "value" is also an integer as compared to the previous entries as strings.
How do I go about in order to be able to output my JSON as shown above? I have tried creating a class that can store all the information, but I cannot reuse the name "value" for both an int and string declaration, and also do not wish to show the variables if their value is null
(Example shown below)
{
"attributes": [
{
"display_type": "",
"trait_type": "Background",
"value": "Green"
},
{
"display_type": "",
"trait_type": "Body",
"value": "Body_1"
},
{
"display_type": "",
"trait_type": "Outfit",
"value": "Beach_Singlet"
},
{
"display_type": "date",
"trait_type": "birthday",
"value": 869270400
}
]
}
You can use object type.
using Newtonsoft.Json;
var list = new AttributeList
{
attributes = new []{
new Attribute
{
trait_type = "Background",
value = "green"
},
new Attribute
{
display_type = "date",
trait_type = "birthday",
value = 869270400
}
}
};
var json = JsonConvert.SerializeObject(list, Formatting.Indented);
Console.WriteLine(json);
public class Attribute
{
public object value { get; set; }
public string trait_type { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string display_type { get; set; }
}
public class AttributeList
{
public Attribute[] attributes { get; set; }
}
Output:
{
"attributes": [
{
"value": "green",
"trait_type": "Background"
},
{
"value": 869270400,
"trait_type": "birthday",
"display_type": "date"
}
]
}
try this
var attributes=new List<Attribute>{
new AttributeString{
trait_type="Background",
value="green"
},
new AttributeInt{
display_type ="date",
trait_type="birthday",
value=869270400
}
};
var jsonSerializerSettings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Objects,
NullValueHandling=NullValueHandling.Ignore,
Formatting=Newtonsoft.Json.Formatting.Indented
};
var json = JsonConvert.SerializeObject(attributes,jsonSerializerSettings);
classes
public class Attribute
{
public string trait_type { get; set; }
public string display_type { get; set; }
}
public class AttributeString:Attribute
{
public string value { get; set; }
}
public class AttributeInt:Attribute
{
public int value { get; set; }
}
public class AttributeList
{
public List<Attribute> attributes { get; set; }
}

C# Custom Json.NET List serialization

I have a List of DataObject classes:
public class DataObject
{
public string Name { get; set; }
public List<Field> Fields { get; set; }
}
}
public class Field
{
public string Name { get; set;}
public string Type { get; set; }
public Object Value { get; set; }
public Field(string name, string type, string value)
{
Name = name;
Type = type;
if(type == "string") Value = (string)value;
else Value = Int32.Parse(value);
}
}
and i would like to serialize List made of DataObject classes to .json file. the way I show below using Json.NET, I have tried few scenarios but didn't find out how to do this.
Example:
Field field1 = new Field("Brand", "string", "Volvo);
Field field2 = new Field("Power", "int", 200);
List<Field> fields = new List<Field>{field1, field2};
DataObject car = new DataObject{
Name = "Car",
Fields = list
}
List<DataObjects> objects = new List<DataObjects>{car};
Result:
{
"Car":{
"Brand" : "Volvo",
"Power" : 200
}
}
Using Newtonsoft.Json, Serialization is rather easy. I took your code and added the following line:
string json = JsonConvert.SerializeObject(objects, Newtonsoft.Json.Formatting.Indented);
and it gave me the following output:
[
{
"Name": "Car",
"Fields": [
{
"Name": "Brand",
"Type": "string",
"Value": "Volvo"
},
{
"Name": "Power",
"Type": "int",
"Value": 200
}
]
}
]

Deserialize from minimal JSON to hierachical objects with mandatory back-references

Assume those hierachical object structure: Tree➻Branch➻Leaf
public class Tree
{
public string Name { get; set; }
public List<Branch> Nodes { get; set; } = new List<Branch>();
}
public class Branch
{
public Branch(Tree parent) { Parent = parent; }
public Tree Parent { get; }
public string Name { get; set; }
public List<Leaf> Nodes { get; set; } = new List<Leaf>();
}
public class Leaf
{
public Leaf(Branch parent) { Parent = parent; }
public Branch Parent { get; }
public string Name { get; set; }
}
A not so simple serialization output would be (using ReferenceLoopHandling.Serialize AND PreserveReferencesHandling = PreserveReferencesHandling.Objects):
{
"$id": "1",
"Name": "AnOakTree",
"Nodes": [
{
"$id": "2",
"Parent": {
"$ref": "1"
},
"Name": "TheLowestBranch",
"Nodes": [
{
"$id": "3",
"Parent": {
"$ref": "2"
},
"Name": "OneOfAMillionLeaf"
}
]
}
]
}
Deserialization works perfectly, nothing to complain! But I'd like to omit reference handling at all to reduce JSON footprint and improve readability.
What's the best way to deserialize JSON without reference informations and invoke the constructors with the correct parameter (the instance of the parent node)? So that the back-references will be recreated by code?

Convert Json object to List

I have this Json string :
"UserProperties": [
{
"Id": "3", "values": [
{ "prop": "1" }
]
},
{
"Id": "4", "values": [
{ "prop": "1" },
{ "prop": "2" },
{ "prop": "3" }
]
}
]
I Want to convert this into some sort of c# string and list value like this:
public list<int> Id { get; set; }
public list<object> values { get; set; }
public int prop { get; set; }
So that i can manipulate my values :
foreach( int i in Id)
{
foreach( object val in values)
{
var str = i + '-' + val.prop;
}
}
So far what i have done is create a class that will contain those Json string. I get this code from an quite similar approach.
Create a wrapper class
class Wrapper {
[JsonProperty("UserPropertiees")]
public ValueSet valueSet { get; set; }
}
class ValueSet{
[JsonProperty("values")]
public List<string> values {get;set;}
public PropertySet propertySet { get; set; }
}
class PropertySet{
[JsonProperty("property")]
public List<string> property {get;set;}
}
I would use Newtonsoft's Json.NET and deserialize your JSON array into your List.
http://www.newtonsoft.com/json/help/html/SerializingCollections.htm
As per Rob Stewart's advice, NewtonSoft.JSON is the best way to go IMO. Here's something you can put in a console app to have a play with:
string json = #"{""UserProperties"": [
{
""Id"": ""3"", ""values"": [
{ ""prop1"": ""1"" }
]
},
{
""Id"": ""4"", ""values"": [
{ ""prop1"": ""1"" },
{ ""prop2"": ""2"" },
{ ""prop3"": ""3"" }
]
}
]}";
dynamic obj = JObject.Parse(json);
foreach (var o in obj.UserProperties)
{
Console.WriteLine(o.Id);
}
Console.ReadLine();
EDIT
As per your comments, here is a more complete example. Try this:
dynamic obj = JObject.Parse(json);
foreach (var o in obj.UserProperties)
{
var sb = new StringBuilder();
sb.Append(o.Id);
sb.Append(":");
bool hasProps = false;
foreach (var value in o.values)
{
if (value.prop1 != null)
{
sb.Append(value.prop1);
sb.Append(',');
hasProps = true;
}
if (value.prop2 != null)
{
sb.Append(value.prop2);
sb.Append(',');
hasProps = true;
}
if (value.prop3 != null)
{
sb.Append(value.prop3);
sb.Append(',');
hasProps = true;
}
}
if (hasProps)
{
sb.Remove(sb.Length - 1, 1); // Remove trailing comma
}
Console.WriteLine(sb.ToString());
}
Console.ReadLine();

Categories

Resources