Json Deserialization, Create duplicate objects - c#

It's easier to show a mock up of the problem rather than trying to explain it first.
internal class Program
{
private static void Main(string[] args)
{
Class1 class1 = new Class1() { Name = "Scott" };
Class2 class2 = new Class2() { Name = "Steve", Objects = new List<Class1>() { class1 } };
Class2 class22 = new Class2() { Name = "Atanas", Objects = new List<Class1>() { class1 } };
List<Class2> list = new List<Class2>() { class2, class22 };
string jSonString = JsonConvert.SerializeObject(list,Formatting.Indented,
new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
List<Class2> result = (List<Class2>) JsonConvert.DeserializeObject(jSonString, typeof(List<Class2>));
if (result[0].Objects[0] == result[1].Objects[0])
{
Console.WriteLine("Correct, its the same object");
}
else
{
Console.WriteLine("Bah!, its a new object");
}
}
}
public class Class1
{
public string Name { get; set; }
}
public class Class2
{
public Class2()
{
Objects = new List<Class1>();
}
public List<Class1> Objects { get; set; }
public string Name { get; set; }
}
The problem is that when the string is deserialized, the "Shared Object" is now duplicated. Before being serialized, the same object (by reference) was in two separate lists. After de serializing both the lists contain separate objects.
Is it possible to have json behave so it doesn't duplicate?
Hope that makes sense
Steve

Yes, if you setup your serializer as follow:
JsonSerializerSettings settings = new JsonSerializerSettings
{
PreserveReferencesHandling = PreserveReferencesHandling.All
};
Json string will be:
{
"$id": "1",
"$values": [
{
"$id": "2",
"Objects": {
"$id": "3",
"$values": [
{
"$id": "4",
"Name": "Scott"
}
]
},
"Name": "Steve"
},
{
"$id": "5",
"Objects": {
"$id": "6",
"$values": [
{
"$ref": "4"
}
]
},
"Name": "Atanas"
}
]
}
And you will see in console:
Correct, its the same object
See http://james.newtonking.com/projects/json/help/index.html?topic=html/T_Newtonsoft_Json_PreserveReferencesHandling.htm

Related

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; }
}

JSON .NET Not Deserializing Refs

I have the following minimal example:
class Program
{
static void Main()
{
var plan = new Plan
{
Steps =
{
new Step
{
Contexts =
{
new Context
{
Name = "1"
},
new Context
{
Name = "2"
}
}
}
}
};
var settings = new JsonSerializerSettings
{ PreserveReferencesHandling = PreserveReferencesHandling.Objects, Formatting = Formatting.Indented };
var json = JsonConvert.SerializeObject(plan, settings);
var deserialized = JsonConvert.DeserializeObject<Plan>(json, settings);
}
}
class Plan
{
public IEnumerable AllContexts => Steps.SelectMany(i => i.Contexts);
[JsonProperty(Order = int.MaxValue)]
public ICollection<Step> Steps { get; set; } = new List<Step>();
}
class Step
{
public ICollection<Context> Contexts { get; set; } = new List<Context>();
}
class Context
{
public string Name { get; set; }
}
In this example deserialized has lost its references upon deserialization and deserialized.AllContexts is a collection of 2 null values.
I can get this working by changing [JsonProperty(Order = int.MaxValue)] to [JsonProperty(Order = int.MinValue)] so the Steps are serialized first - but in my scenario I want the actual JSON to have all its properties on the flat AllContexts array and for the Steps to only have $refs like this:
{
"$id": "1",
"AllContexts": [
{
"$id": "2",
"Name": "1"
},
{
"$id": "3",
"Name": "2"
}
],
"Steps": [
{
"$id": "4",
"Contexts": [
{
"$ref": "2"
},
{
"$ref": "3"
}
]
}
]
}
This seems like a bug in JSON .NET - is there a way to work around it?

Json.NET Not Respecting PreserveReferencesHandling on Deserialization

I have a list of Team that I am trying to deserialise.
class Team
{
public string TeamName {get; set;};
private List<FootballPlayer> _fPlayers = new List<FootballPlayer>();
public List<FootballPlayer> FPlayers
{
get => _fPlaters;
}
}
class FootballPlayer
{
private Team _team;
public string Name { get; set; }
public Team Team
{
get => _team;
}
[JsonConstructor]
public FootballPlayer(Team team)
{
_team = team;
}
}
I have the following JSON settings:
JsonSerializerSettings serializerSettings = new JsonSerializerSettings()
{
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
Formatting = Formatting.Indented
};
When I look at the serialised output, it appears correct, and the references between nodes are properly represented.
{
"$id": "1",
"TeamName": "Test",
"FPlayers": [
{
"$id": "2",
"Team": {
"$ref": "1"
},
"Name": "Leo Messi"
}
]
}
When the data is deserialised, the property "Team" in the player "Leo Messi" is null.
How can I deserialise this JSON in such a way that the property "Team" of "Leo Messi" is not null?
JsonConvert.DeserializeObject<List<Team>>(JsonString, serializerSettings);

How to configure the Json serializer, to allow Circular References in Azure Functions (V2)?

Suppose, I have a object that has a sub property that references itself.
public class Person
{
public Person Parent { get; set; }
public string Name { get; set; }
public ObservableCollection<Person> Children { get; set; } = new ObservableCollection<Person>();
}
When I try to return it,
var pp = new Person();
pp.Name = "TONY";
pp.Children.Add(new Person()
{
Parent = pp,
Name = "ESTHER"
});
return new JsonResult(pp);
it comes truncated, some Exception happens while serializing it.
{"parent":null,"name":"TONY","children":[{
SyntaxError: JSON.parse: end of data while reading object contents at line 1 column 43 of the JSON data
On ASP Net we could do this:
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling
= Newtonsoft.Json.ReferenceLoopHandling.Serialize;
config.Formatters.JsonFormatter.SerializerSettings.PreserveReferencesHandling
= Newtonsoft.Json.PreserveReferencesHandling.Objects;
How can I configure Azure Function Json Serializer, to allow it?
Looking at the overloaded methods, I found:
then I used it with this code:
var serializerSettings = new Newtonsoft.Json.JsonSerializerSettings()
{
ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize,
PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.All
};
return new JsonResult(p, serializerSettings);
And it worked.
{
"$id": "1",
"Parent": null,
"Name": "TONY",
"Children": {
"$id": "2",
"$values": [
{
"$id": "3",
"Parent": {
"$ref": "1"
},
"Name": "ESTHER",
"Children": {
"$id": "4",
"$values": []
}
}
]
}
}
You can also get to config using:
var config = req.GetConfiguration();
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize;
config.Formatters.JsonFormatter.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;
The only downside is that you need to create on each function but you could add a custom attribute to make that easier.

JSON .Net not respecting PreserveReferencesHandling on Deserialization

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.

Categories

Resources