1

I have the following piece of code:

 JObject my_obj = JsonConvert.DeserializeObject<JObject>(ReceivedJson);
 ParseJson(my_obj); //method to store all the nested "keys" and the "id" values

     public void ParseJson(JObject obj)
        {

            foreach (KeyValuePair<string, JToken> sub_obj in (JObject)obj["Soccer"])
            {
                Console.WriteLine(sub_obj.Key);
            }
        }
//this does not work well as I cant access all the nested keys :/

I am receiving a json in the following format. It could be nested on several levels and I want to be able to store the nested "keys" and their respective "T_id" value in a dictionary.

The json is as follows:

{
   "Soccer":{
      "T_id":0,
      "T_state":"valid",
      "Clubs":{
         "ClubA":{
            "T_id":"1",
            "T_state":"Champs"
         },
         "ClubB":{
            "T_id":"2",
            "T_state":"Runnerups"
         }
      },
      "Subs":{
         "SubA":{
            "T_id":"3",
            "T_state":"Unfit",
            //this is nested key
            "SubE":{
               "T_id":"3",
               "T_state":"Unfit"
            }
         }
      },
      "Subs_Used":{
         "SubK":{
            "T_id":"3",
            "T_state":"Unfit"
         }
      }
      //many more nested n-levels   
   }
}

I want to be able to extract the "keys" and create a nested structure like this:

>Soccer
  >Clubs
    ClubA
    ClubB
  >Subs
    SubA
  >Subs_Used
    SubK

where each node has two fields, { string key, int T_id }

The "keys" could be nested deeply and I want to have a generic method which allows me to create this hierarchy while iterating over the JObject.

Is there a simple approach to do this? I am really lost and would appreciate help to make progress.

Laura Smith
  • 293
  • 3
  • 13
  • Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/194200/discussion-on-question-by-laura-smith-iterate-over-a-json-input-and-create-the-t). – Samuel Liew May 30 '19 at 23:30

1 Answers1

1

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<KeyIdObject> 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
{
    /// <summary>
    /// Enumerates through all descendants of the given element, returning the topmost elements that match the given predicate
    /// </summary>
    /// <param name="root"></param>
    /// <param name="filter"></param>
    /// <returns></returns>
    public static IEnumerable<TJToken> TopDescendantsWhere<TJToken>(this JToken root, Func<TJToken, bool> predicate) where TJToken : JToken
    {
        if (predicate == null)
            throw new ArgumentNullException();
        return GetTopDescendantsWhere<TJToken>(root, predicate, false);
    }

    static IEnumerable<TJToken> GetTopDescendantsWhere<TJToken>(JToken root, Func<TJToken, bool> 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<KeyIdObject> like so:

public partial class KeyIdObject
{
    public static List<KeyIdObject> ToIdObjects(JToken root)
    {
        return root.TopDescendantsWhere<JObject>(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<KeyIdObject> Children { get; set; }
}

public partial class KeyIdObject
{
    public static List<KeyIdObject> ToIdObjects(JToken root)
    {
        return root.TopDescendantsWhere<JObject>(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<string, IdObject> Children { get; set; }
}

public partial class IdObject
{
    public static Dictionary<string, IdObject> ToIdObjects(JToken root)
    {
        return root.TopDescendantsWhere<JObject>(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<T>. 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<KeyItemBase>()) { }

    public KeyItemBase(string key, IEnumerable<KeyItemBase> children)
    {
        this.m_key = key;
        this.m_children = new ObservableCollection<KeyItemBase>(children);
    }

    string m_key;
    public string key 
    { 
        get { return m_key; }
        set
        {
            m_key = value;
            RaisedOnPropertyChanged("key");
        }
    }

    ObservableCollection<KeyItemBase> m_children;
    public ObservableCollection<KeyItemBase> 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<KeyItemBase> 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<KeyItemBase> 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<KeyItemBase> children)
    {
        if (id == null)
            return new KeyItem(name, children);
        else
            return new KeyIdItem(name, children, id.Value);
    }

    public static IEnumerable<KeyItemBase> ToKeyObjects(JToken root)
    {
        return root.TopDescendantsWhere<JObject>(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<KeyItemBase>(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.

dbc
  • 104,963
  • 20
  • 228
  • 340