Generic Tree to JSON - c#

I have a generic tree structure representing employees in an organization chart.
The tree consists of a graph of custom Node<Person> objects which have references to each other and other properties like Level showing their level in the tree, parent, sibilings, etc.
I have to serialize a portion of this organization chart from a specific Person on down through everyone below them, and I have a method on the Node object called SelfAndDescendants() that returns an IEnumerable<Node<Person>>.
So basically I locate the specific person's Node in the tree, then get them and all their descendants in an IEnumerable. This part works fine.
That's where I am stuck. I now need to get this IEnumerable set of Nodes into hierarchical JSON.
My first attempt was just to throw it straight at the JSON serializer but that does not work (nor did I really expect it to), because it's a set of generic Node objects. There is a Value property on the Node object that will return a Person object ... which is what I need to get into the JSON (just the name).
string json = JsonConvert.SerializeObject(personNode.SelfAndDescendants.ToList());
This obviously is trying to serialize a List<Node<Person>> at this point which is not what I need. All the JSON return needs is a hierarchical format with simple the Person object's Name. Nothing else.
Do I have to do anything manually here in a loop to build custom JSON and return that?
This is not a duplicate of this post, as I am dealing with a generic recursive tree here and not a simple generic data structure.
Do I have to implement a custom JsonConverter? How does this work with a series of Node objects in a tree?
The Node class has all sorts of properties but it basically looks like this:
public class Node<T> : IEqualityComparer, IEnumerable<T>, IEnumerable<Node<T>> {
public Node(T value) {
Value = value;
}
public Node<T> this[int index] {
get {
return _children[index];
}
}
public Node<T> Add(T value, int index = -1) {
var childNode = new Node<T>(value);
Add(childNode, index);
return childNode;
}
public IEnumerable<Node<T>> SelfAndDescendants {
get {
return this.ToIEnumarable().Concat(Children.SelectMany(c => c.SelfAndDescendants));
}
}
}
The Person class is just a POCO class representing a person. This class is already serializing out to JSON correctly for another part of the system.
[JsonObject]
public class Person {
public string Title { get; set; }
public DateTime DateOfBirth { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public Gender Gender { get; set; }
public List<StreetAddress> Addresses { get; set; }
... etc
}
The desired output is an organization chart, showing people's levels in JSON. So employee under their boss, that boss under their boss, etc etc.
The JSON is extremely simple in this regard, it's just the persons name and title. It can even just be a single string per employee.

Yes, given your constraints, I would say that creating a custom JsonConverter is an appropriate solution for this situation. It is actually pretty straightforward to write, so long as Node<T> has public properties to allow at least read access to the Value and the Children. You don't have to worry about looping; the serializer will call back into the converter for each child as it iterates over the Children collection via the JArray.FromObject call.
Here's how I would write it:
public class OrgChartConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Node<Person>));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Node<Person> node = (Node<Person>)value;
JObject obj = new JObject();
obj.Add("Name", node.Value.Name);
obj.Add("Subordinates", JArray.FromObject(node.Children, serializer));
obj.WriteTo(writer);
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then, use the converter like this:
var settings = new JsonSerializerSettings
{
Converters = new List<JsonConverter> { new OrgChartConverter() },
Formatting = Formatting.Indented
};
string json = JsonConvert.SerializeObject(rootNode, settings);
Here is a demo: https://dotnetfiddle.net/BfdFdW

You want to describe how this class should be serialized by adding serialization attributes on the class and on the members needed and then serialize to string
string nodes = JsonConvert.SerializeObject<Node<Person>>(personNode);
[JsonObject]
public class Node<T> : IEqualityComparer, IEnumerable<T>, IEnumerable<Node<T>> {
[JsonProperty]
public IEnumerable<Node<T>> Children { get { return _children; } }
...
}
JSON.NET Serialization Attributes

Related

How can I serialize a property in an inherited ICollection<T> class?

I have a class that presented like
public class ItemCollection<T> : ICollection<T> {
public ItemCollection() {
Items = new List<T>();
}
public List<T> Items { get; set; }
...
}
Now it will be serialized into:
{
"Property": [{...}]
}
But I want the result is like:
{
"Property": {"Items": [{...}]}
}
Sorry for the missing information of this question.
I now stuck in serialization when using System.Text.Json.
In Newtonsoft.Json, I use [JsonObject] to annotate this class so it can serialization correctly into json with "Items": value, but I don't know how to serialize the Items property using System.Text.Json.
I have some classes inherited this class and the inheritances will be as properties in other classes.
Solution:
Thank you for every one that answered this question, I have found a solution to solve this. I create a ConverterFactory to resolve the needed types to create the converters. In the converter, I create new JsonObject and use Reflection to create the properties in the type, after this, I serialize the JsonObject so I can get the correct result.
Thank you for your question, but I did not found any difference between the serialization of Newtonsoft and System.Text.Json.
Perhabs you can provide an example in code to show us the problem directly.
With your provided information I got with following code example the same results
static void Main(string[] args)
{
ItemCollection<Person> list = new ItemCollection<Person> {new Person(){ FirstName = "FirstName", Name = "Name"}};
string jsonString = JsonSerializer.Serialize(list);
Console.WriteLine(jsonString);
string jsonString2 =Newtonsoft.Json.JsonConvert.SerializeObject(list);
Console.WriteLine(jsonString2);
Console.ReadLine();
}
[{"Name":"Name","FirstName":"FirstName"}]
There is no built-in attribute corresponding to Newtonsoft's JsonObjectAttribute that will force a collection to be serialized as a JSON object.1. And there is no public equivalent to IContractResolver that can be overridden to customize serialization metadata. Thus you will need to create a custom JsonConverter to serialize the properties of your ItemCollection<T>, such as the following:
[System.Text.Json.Serialization.JsonConverter(typeof(ItemCollectionJsonConverter))]
public partial class ItemCollection<T> : ICollection<T> {
internal ItemCollection(List<T> items) { // I added this for use by the converter
Items = items ?? throw new ArgumentNullException();
}
public ItemCollection() {
Items = new List<T>();
}
public List<T> Items { get; set; }
// Remainder omitted
}
public class ItemCollectionJsonConverter : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert) => GetItemCollectionValueType(typeToConvert) != null;
public override JsonConverter CreateConverter(Type type, JsonSerializerOptions options)
=> (JsonConverter)Activator.CreateInstance(typeof(ItemCollectionJsonConverterInner<>).MakeGenericType(GetItemCollectionValueType(type)!))!;
static Type? GetItemCollectionValueType(Type type) =>
(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ItemCollection<>)) ? type.GetGenericArguments()[0] : null;
class ItemCollectionJsonConverterInner<T> : JsonConverter<ItemCollection<T>>
{
class ItemCollectionDTO
{
public List<T>? Items { get; set; }
// Add other properties here
}
public override ItemCollection<T> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
// TODO: Decide whether to throw on null
new ItemCollection<T>((JsonSerializer.Deserialize<ItemCollectionDTO>(ref reader, options)?.Items) ?? throw new JsonException());
public override void Write(Utf8JsonWriter writer, ItemCollection<T> value, JsonSerializerOptions options) =>
JsonSerializer.Serialize(writer, new ItemCollectionDTO { Items = value.Items }, options);
}
}
Notes:
I added the converter directly to ItemCollection<T> via attributes, but you could add it to JsonSerializerOptions.Converters if you prefer.
Many serializers will not serialize collection properties. See e.g. XmlSerializer doesn't serialize everything in my class for another.
Adding properties to collections isn't really a recommended practice; see Why not inherit from List<T>? for a discussion why.
Demo fiddle here.
1 The list of all System.Text.Json serialization attributes is documented here.

Json.Net - performant deserialization onto interface based data structure?

As soon as I don't have only concrete types in the data structures I deserialize a HUGE json tree structure into, it starts using enormous amounts of memory, but its memory footprint stays relatively slim when deserializing into entirely concrete types… is there an elegant workaround for this?
The json I get is generated elsewhere, so I have no influence as to the format I get it in (it's a tree structure, similar to the code example below if it were serialized to json directly), and in the worst case about 250-300MB of it.
My data structure for mapping it used to look somewhat like the following example (structs in some places, though)
public class Node : INode
{
[JsonConverter(typeof(NodeTypeConverter<IInnerNode, InnerNodeType1>))]
public List<INodeInner> InnerNodes { get; set; }
}
public class InnerNodeNodeType1 : INode
{
[JsonConverter(typeof(NodeTypeConverter<IInnerNode, InnerNodeType2>))]
public List<INodeInner> InnerNodes { get; set; }
// some other properties
}
public class InnerNodeNodeType2 : INode
{
[JsonConverter(typeof(NodeTypeConverter<IInnerNode, InnerNodeType3>))]
public List<INodeInner> InnerNodes { get; set; }
// some even different properties
}
…
however, I did not find a way to map this without bringing the PC it runs on to its knees, especially memory-wise (apart from that, in some places with List<interface> I didn't even get json.Net to use the converter, it threw an error Could not create an instance of type {type}. Type is an interface or abstract class and cannot be instantiated. before even checking the converter class…).
So now, I changed it to all-concrete types/Lists of concrete type instances instead of the interfaces plus a converter, and it runs with MUCH less of a memory footprint (orders of magnitude!). But it's inelegant, because this way, I can't reuse most of the classes for different kinds of trees I'll have to use in other places of the program, which are similar, but subtly different.
Is there an elegant solution for this?
PS: Thanks for reading this far! This question might not be perfectly posed and/or contain all and any type of info you might need to suggest a solution. I've found, however, that anytime I tried to cover all bases and anticipate all further questions, I got no responses at all, so that's my attempt to ask differently this time… :P
You don't provide a concrete example of your problem, but you did write I changed it to all-concrete types/Lists of concrete type instances instead of the interfaces plus a converter, and it runs with MUCH less of a memory footprint (orders of magnitude!). It sounds as though you must be loading large chunks of JSON into memory in some intermediate representation, such as a string for the entire JSON or a JArray for the complete contents of your public List<INodeInner> InnerNodes arrays. After that you are converting the intermediate representation(s) into your final objects.
What you need to do is to avoid loading any intermediate representations, or if you must do so, load only the smallest possible JSON chunk at once. The following is an example implementation:
public interface INode
{
List<INodeInner> InnerNodes { get; set; }
}
public interface INodeInner : INode
{
}
public class Node : INode
{
[JsonProperty(ItemConverterType = typeof(InterfaceToConcreteConverter<INodeInner, InnerNodeNodeType1>))]
public List<INodeInner> InnerNodes { get; set; }
}
public class InnerNodeNodeType1 : INodeInner
{
[JsonProperty(ItemConverterType = typeof(InterfaceToConcreteConverter<INodeInner, InnerNodeNodeType2>))]
public List<INodeInner> InnerNodes { get; set; }
// some other properties
public int Type1Property { get; set; }
}
public class InnerNodeNodeType2 : INodeInner
{
[JsonProperty(ItemConverterType = typeof(InterfaceToConcreteConverter<INodeInner, InnerNodeNodeType3>))]
public List<INodeInner> InnerNodes { get; set; }
// some even different properties
public int Type2Property { get; set; }
}
public class InnerNodeNodeType3 : INodeInner
{
[JsonProperty(ItemConverterType = typeof(InterfaceToConcreteConverter<INodeInner, InnerNodeNodeType3>))]
public List<INodeInner> InnerNodes { get; set; }
// some even different properties
public int Type3Property { get; set; }
}
public class InterfaceToConcreteConverter<TInterface, TConcrete> : JsonConverter where TConcrete : TInterface
{
public InterfaceToConcreteConverter()
{
// TConcrete should be a subtype of an abstract type, or an implementation of an interface. If they
// are identical an infinite recursion could result, so throw an exception.
if (typeof(TInterface) == typeof(TConcrete))
throw new InvalidOperationException(string.Format("typeof({0}) == typeof({1})", typeof(TInterface), typeof(TConcrete)));
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(TInterface);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return serializer.Deserialize(reader, typeof(TConcrete));
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And then, to load:
Node root;
var settings = new JsonSerializerSettings
{
// Whatever settings you need.
};
using (var stream = File.OpenRead(fileName))
using (var textReader = new StreamReader(stream))
using (var reader = new JsonTextReader(textReader))
{
root = JsonSerializer.CreateDefault(settings).Deserialize<Node>(reader);
}
Notes:
Rather than writing a converter for the entire List<INodeInner> InnerNodes and applying it with [JsonConverter(typeof(NodeTypeConverter<IInnerNode, InnerNodeType1>))], I created a converter for each list item and apply it by setting JsonPropertyAttribute.ItemConverterType:
[JsonProperty(ItemConverterType = typeof(InterfaceToConcreteConverter<INodeInner, InnerNodeNodeType1>))]
public List<INodeInner> InnerNodes { get; set; }
Thus simplifies the converter and guarantees that, if the converter needs to preload the JSON into an intermediate JToken, only one list item is preloaded and converted at once.
Since, in your example, the type of INodeInner is fixed for each type of INode, it isn't even necessary to preload individual list items. Instead, in JsonConverter.ReadJson(), deserialize directly from the incoming JsonReader using the correct concrete type:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return serializer.Deserialize(reader, typeof(TConcrete));
}
As explained in Newtonsoft Performance Tips: Optimize Memory Usage, when deserializing a large JSON file, deserialize directly from a stream:
using (var stream = File.OpenRead(fileName))
using (var textReader = new StreamReader(stream))
using (var reader = new JsonTextReader(textReader))
{
root = JsonSerializer.CreateDefault(settings).Deserialize<Node>(reader);
}
Sample fiddle showing this working.

Deserializing name / value pairs to objects

I have a collection of name / value pairs where they are defined with the words name and value just like a Key/Value object, i.e.
[{"Name":"ActivityId","DataType":1,"Value":"a7868f8c-07ac-488d-a414-714527c2e76f"},
{"Name":"Address1","DataType":2,"Value":"123 Main St"}]
If I had an object like:
class Request
{
public Guid ActivityId { get; set; }
public string Address1 {get; set; }
}
How can I deserialize this to the class above?
Should I consider a custom converter? Does Json.NET have something built-in? Is there a way to decorate the properties with an attribute that I'm missing? Would it be easier to customize the serialization?
I'm trying to avoid pulling the data for each property from a Dictionary, which would be the easy route, but would require me to do this with each custom implementation. I would prefer to do this in a base class in a single method using Json.NET (or something in the .NET framework).
I've searched quite a bit, and most examples are real name/value pairs, not prefixed with name and value, i.e.
[{"ActivityId":"a7868f8c-07ac-488d-a414-714527c2e76f"}]
Any ideas?
This can be done in a straightforward manner with a custom JsonConverter like the one below. The converter works by first transforming the array of name-value pairs into a JObject with properties mirroring the pairs, then populating the target object from the JObject using the serializer's built-in Populate method.
class NameValueConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Load the array of name-value pairs and transform into a JObject.
// We are assuming all the names will be distinct.
JObject obj = new JObject(
JArray.Load(reader)
.Children<JObject>()
.Select(jo => new JProperty((string)jo["Name"], jo["Value"]))
);
// Instantiate the target object and populate it from the JObject.
object result = Activator.CreateInstance(objectType);
serializer.Populate(obj.CreateReader(), result);
return result;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// WriteJson is not called when CanWrite returns false
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
// We only want this converter to handle classes that are expressly
// marked with a [JsonConverter] attribute, so return false here.
// (CanConvert is not called when [JsonConverter] attribute is used.)
return false;
}
}
To use the converter, just add a [JsonConverter] attribute to the target class:
[JsonConverter(typeof(NameValueConverter))]
class Request
{
public Guid ActivityId { get; set; }
public string Address1 {get; set; }
}
Then, you can deserialize as you normally would:
Request req = JsonConvert.DeserializeObject<Request>(json);
Fiddle: https://dotnetfiddle.net/tAp1Py

Deserialize JSON object into C# class that wraps typed Dictionary field

I have a JSON object that I get from my REST API server that looks like this:
"settings": {
"assets.last_updated_at": "2016-08-24T23:40:26.442Z",
"data.version": 34
}
Normally I'd deserialize the object to a Dictionary of string to object, but I'd like to provide helper methods to get strongly typed versions of the values. I'm using JSON.NET to deserialize the JSON object. Here's what I've got so far:
[JsonConverter(typeof(SettingsJsonConverter))]
public class Settings
{
[JsonIgnore]
public Dictionary<string, string> Entries { get; private set; }
public Settings()
{
this.Entries = new Dictionary<string, string>();
}
}
In order to wrap the dictionary within a class, it seems like I needed to create a custom JsonConverter, which currently looks like this:
public class SettingsJsonConverter : JsonConverter
{
#region implemented abstract members of JsonConverter
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject settingsObj = JObject.Load(reader);
Settings settings = new Settings();
foreach(KeyValuePair<string, JToken> entry in settingsObj)
{
settings.Entries.Add(entry.Key, entry.Value.ToString());
}
return settings;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Settings);
}
#endregion
}
Now obviously I could create methods in my Settings class like, GetDate that takes the value for a given key and creates a new DateTime object with the ISO string value. However, JSON.NET already provides facilities to do what I want to do, so I'd rather use its built-in deserialization for any values I iterate over, instead of doing it myself.
It seems as though the place to do this would be inside the foreach loop in SettingsJsonConverter#ReadJson, but I'm not quite sure how to get the Type (not a JTokenType, a standard C# Type) of a given JToken so I can pass it into JToken#ToObject(Type). Or, how can I iterate over a JsonReader to populate my internal Entries dictionary with already deserialized values, such that I just need to do simple casts in my helper methods in Settings? Here's how I'd like my Settings class to look:
[JsonConverter(typeof(SettingsJsonConverter))]
public class Settings
{
[JsonIgnore]
public Dictionary<string, object> Entries { get; private set; }
public Settings()
{
this.Entries = new Dictionary<string, object>();
}
public DateTime GetDate(string key)
{
return (DateTime)this.Entries[key];
}
}
Am I even doing this correctly? It almost feels like there's some simple way I'm overlooking.
You can directly deserialize into a Dictionary<string, JToken> and use type casts as you need them so you don't need a Settings class or a custom converter. It doesn't look as nice as your solution, but the code is simpler:
var settings = JsonConvert.DeserializeObject<Dictionary<string, JToken>>(json);
var date = (DateTime) settings["key"];
If you want to keep your Settings class, I'd suggest that you use the above line to deserialize the dictionary and create a new constructor in Settings with a dictionary parameter, so you end up with two constructors, one that creates a new dictionary, and one that uses a given dictionary to create the new one. (Don't re-use the parameter dictionary itself, you never know what the caller will do to the dictionary afterwards.)
public Settings(Dictionary<string, JToken> dict)
{
Entries = new Dictionary<string, JToken>(dict);
}
By the way, you can remove the JsonIgnore attribute because you have a custom converter class.
First of all we have to make a model for your corresponding json.
{
"settings": {
"assets.last_updated_at": "2016-08-24T23:40:26.442Z",
"data.version": 34
}
}
CSharp Model:
public class Settings
{
[JsonProperty("assets.last_updated_at")]
public string assets_last_updated_at { get; set; }
[JsonProperty("data.version")]
public int data_version { get; set; }
// Add other property here...
}
public class RootObject
{
public Settings settings { get; set; }
}
Deserialize Json string to Model :
RootObject rootObject = JsonConvert.DeserializeObject<RootObject>("Put your Json String");
Hope this help.

Deserialize derived classes using Json.net without using JObject

I have a large json dataset that I need to deserialize. I am using Json.net's JsonTextReader to read the data.
My problem is that I need to deserialize some derived classes, so I need to be able to "look ahead" for a particular property defining my data type. In the example below, the "type" parameter is used to determine the object type to deserialize.
{
type: "groupData",
groupParam: "groupValue1",
nestedObject:
{
type: "groupData",
groupParam: "groupValue2",
nestedObject:
{
type: "bigData",
arrayData: [ ... ]
}
}
My derived objects can be heavily nested and very deep. Loading the entire dataset in memory is not desired since it will require much memory. Once I get down to the "bigData" object, I will be processing the data (such as the array in the example above), but it will not be stored in memory (it is too big).
All solutions to my problem that I have seen so far have utilized JObject to deserialize the partial objects. I want to avoid using JObject because it will deserialize every object down the hierarchy repeatedly.
How can I solve my deserialization issue?
Is there any way to search ahead for the "type" parameter, then backtrack to the start of the object's { character to start processing?
I not aware of anyway to prempt the loading in of the object in order to specify a lookahead (at least not in Json.NET) but you could use the other attribute based configuration items at your disposal in order to ignore unwanted properties:
public class GroupData {
[JsonIgnore]
public string groupParam { get; set; }
[JsonIgnore]
public GroupData nestedObject { get; set; }
public string[] arrayData { get; set; }
}
Alternatively, you can give custom creation converters a try:
For example..
public class GroupData {
[JsonIgnore]
public string groupParam { get; set; }
[JsonIgnore]
public GroupData nestedObject { get; set; }
}
public class BigData : GroupData {
public string[] arrayData { get; set; }
}
public class ObjectConverter<T> : CustomCreationConverter<T>
{
public ObjectConverter() { }
public override bool CanConvert(Type objectType)
{
return objectType.Name == "BigData";
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Some additional checks/work?
serializer.Populate(reader, target);
}
}

Categories