Iterate over a json input and create the treeview like hierarchy based on the “keys” taking account of the children “keys”

后端 未结 1 1395
猫巷女王i
猫巷女王i 2021-01-22 12:02

I have the following piece of code:

 JObject my_obj = JsonConvert.DeserializeObject(ReceivedJson);
 ParseJson(my_obj); //method to store all the n         


        
1条回答
  •  生来不讨喜
    2021-01-22 12:49

    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.

    0 讨论(0)
提交回复
热议问题