I have the following piece of code:
JObject my_obj = JsonConvert.DeserializeObject(ReceivedJson);
ParseJson(my_obj); //method to store all the n
What you want to do is to map your deeply nested JSON into a c# tree where each node has two properties -- a string key
and a long T_id
-- as well as a collection of children of the same type.
You could model this as follows, using a list:
public partial class KeyIdObject
{
public string key { get; set; }
public long T_id { get; set; }
public List Children { get; set; }
}
Once you have you data model, you need to use a recursive algorithm to generate your nodes. A related algorithms is shown in Searching for a specific JToken by name in a JObject hierarchy, but you need a two-stage recursion:
Descend through the JToken
hierarchy until you find a JObject
with a T_id
property.
Once you have found a match, construct a KeyIdObject
for it and populate its list of children by searching the matching JObject's children using a nested recursive search.
Then move on to the matches's next sibling in the outer recursive search.
This can be accomplished by introducing an extension method that searches for the topmost descendants of a given JToken
that match a given condition:
public static partial class JsonExtensions
{
///
/// Enumerates through all descendants of the given element, returning the topmost elements that match the given predicate
///
///
///
///
public static IEnumerable TopDescendantsWhere(this JToken root, Func predicate) where TJToken : JToken
{
if (predicate == null)
throw new ArgumentNullException();
return GetTopDescendantsWhere(root, predicate, false);
}
static IEnumerable GetTopDescendantsWhere(JToken root, Func predicate, bool includeSelf) where TJToken : JToken
{
if (root == null)
yield break;
if (includeSelf)
{
var currentOfType = root as TJToken;
if (currentOfType != null && predicate(currentOfType))
{
yield return currentOfType;
yield break;
}
}
var rootContainer = root as JContainer;
if (rootContainer == null)
yield break;
var current = root.First;
while (current != null)
{
var currentOfType = current as TJToken;
var isMatch = currentOfType != null && predicate(currentOfType);
if (isMatch)
yield return currentOfType;
// If a match, skip children, but if not, advance to the first child of the current element.
var next = (isMatch ? null : current.FirstChild());
if (next == null)
// If no first child, get the next sibling of the current element.
next = current.Next;
// If no more siblings, crawl up the list of parents until hitting the root, getting the next sibling of the lowest parent that has more siblings.
if (next == null)
{
for (var parent = current.Parent; parent != null && parent != root && next == null; parent = parent.Parent)
{
next = parent.Next;
}
}
current = next;
}
}
static JToken FirstChild(this JToken token)
{
var container = token as JContainer;
return container == null ? null : container.First;
}
}
Then, you can use it to generate a recursive List
like so:
public partial class KeyIdObject
{
public static List ToIdObjects(JToken root)
{
return root.TopDescendantsWhere(o => o["T_id"] != null)
.Select(o => new KeyIdObject { key = ((JProperty)o.Parent).Name, T_id = (long)o["T_id"], Children = ToIdObjects(o) })
.ToList();
}
}
Demo fiddle #1 here, which generates the following structure:
[
{
"key": "Soccer",
"T_id": 0,
"Children": [
{
"key": "ClubA",
"T_id": 1
},
{
"key": "ClubB",
"T_id": 2
},
{
"key": "SubA",
"T_id": 3,
"Children": [
{
"key": "SubE",
"T_id": 3
}
]
},
{
"key": "SubK",
"T_id": 3
}
]
}
]
However, in your JSON some of your object node(s), specifically "Clubs"
and "Subs"
, do not have a T_id
property. Thus, they can't be captured into the node hierarchy as there is no way to populate the long T_id
value. If you do need to capture these nodes, you can modify your data model to have a nullable value for the id and capture the intermediate nodes as follows:
public partial class KeyIdObject
{
public string key { get; set; }
public long? T_id { get; set; }
public List Children { get; set; }
}
public partial class KeyIdObject
{
public static List ToIdObjects(JToken root)
{
return root.TopDescendantsWhere(o => true)
.Select(o => new KeyIdObject { key = ((JProperty)o.Parent).Name, T_id = (long?)o["T_id"], Children = ToIdObjects(o) })
.ToList();
}
}
Demo fiddle #2 here.
Finally, if you are sure your keys are unique at any given level, you could use a dictionary instead of a list, like so:
public partial class IdObject
{
public long T_id { get; set; }
public Dictionary Children { get; set; }
}
public partial class IdObject
{
public static Dictionary ToIdObjects(JToken root)
{
return root.TopDescendantsWhere(o => o["T_id"] != null)
.ToDictionary(o => ((JProperty)o.Parent).Name,
o => new IdObject { T_id = (long)o["T_id"], Children = ToIdObjects(o) });
}
}
Demo fiddle #3 here.
Note that, in all cases, I chose long
instead of int
for the T_id
for safety.
Update
If you are going to bind this into a WPF TreeView or something similar like a Syncfusion.Xamarin.SfTreeView, you will want to implement INotifyPropertyChanged
and use ObservableCollection
. You may also want to use a different ItemTemplate
for nodes with and without T_id
values, in which case you can define a different c# POCO for each case. The following is one example:
public abstract partial class KeyItemBase : INotifyPropertyChanged
{
public KeyItemBase() : this(null, Enumerable.Empty()) { }
public KeyItemBase(string key, IEnumerable children)
{
this.m_key = key;
this.m_children = new ObservableCollection(children);
}
string m_key;
public string key
{
get { return m_key; }
set
{
m_key = value;
RaisedOnPropertyChanged("key");
}
}
ObservableCollection m_children;
public ObservableCollection Children { get { return m_children; } }
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisedOnPropertyChanged(string _PropertyName)
{
var changed = PropertyChanged;
if (changed != null)
{
changed(this, new PropertyChangedEventArgs(_PropertyName));
}
}
}
public abstract partial class KeyItemBase
{
// Generate clean JSON on re-serialization.
public bool ShouldSerializeChildren() { return Children != null && Children.Count > 0; }
}
public sealed class KeyItem : KeyItemBase
{
// Use for a JSON object with no T_id property.
// Bind an appropriate SfTreeView.ItemTemplate to this type.
public KeyItem() : base() { }
public KeyItem(string key, IEnumerable children) : base(key, children) { }
}
public class KeyIdItem : KeyItemBase
{
// Use for a JSON object with a T_id property.
// Bind an appropriate SfTreeView.ItemTemplate to this type.
public KeyIdItem() : base() { }
public KeyIdItem(string key, IEnumerable children, long t_id) : base(key, children) { this.m_id = t_id; }
long m_id;
public long T_id
{
get { return m_id; }
set
{
m_id = value;
RaisedOnPropertyChanged("T_id");
}
}
}
public static class KeyItemFactory
{
public static KeyItemBase ToKeyObject(string name, long? id, IEnumerable children)
{
if (id == null)
return new KeyItem(name, children);
else
return new KeyIdItem(name, children, id.Value);
}
public static IEnumerable ToKeyObjects(JToken root)
{
return root.TopDescendantsWhere(o => true)
.Select(o => ToKeyObject(((JProperty)o.Parent).Name, (long?)o["T_id"], ToKeyObjects(o)));
}
}
Which you would use as follows:
var items = new ObservableCollection(KeyItemFactory.ToKeyObjects(root));
// Now bind items to your ItemsSource
// https://help.syncfusion.com/cr/cref_files/xamarin/Syncfusion.SfTreeView.XForms~Syncfusion.XForms.TreeView.SfTreeView~ItemsSource.html
Demo fiddle #4 here.