Выполните итерацию по входу json и создайте древовидную иерархию на основе "ключей" с учетом дочерних "ключей"

У меня есть следующий кусок кода:

 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 :/

Я получаю JSON в следующем формате. Он может быть вложен в несколько уровней, и я хочу иметь возможность хранить вложенные "ключи" и соответствующие им значения "T_id" в словаре.

JSON выглядит следующим образом:

{
   "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   
   }
}

Я хочу иметь возможность извлечь "ключи" и создать вложенную структуру, например:

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

где каждый узел имеет два поля, { string key, int T_id }

"Ключи" могут быть глубоко вложены, и я хочу иметь универсальный метод, который позволяет мне создавать эту иерархию, перебирая JObject.

Есть ли простой подход для этого? Я действительно потерян и был бы признателен за помощь в достижении прогресса.

1 ответ

Решение

То, что вы хотите сделать, это отобразить ваш глубоко вложенный JSON в дерево AC#, где каждый узел имеет два свойства: string key и long T_id - а также коллекция детей одного типа.

Вы можете смоделировать это следующим образом, используя список:

public partial class KeyIdObject
{
    public string key { get; set; }
    public long T_id { get; set; }
    public List<KeyIdObject> Children { get; set; }
}

Если у вас есть модель данных, вам нужно использовать рекурсивный алгоритм для генерации ваших узлов. Связанные алгоритмы показаны в разделе Поиск определенного JToken по имени в иерархии JObject, но вам нужна двухэтапная рекурсия:

  • Спуск через JToken иерархия, пока вы не найдете JObject с T_id имущество.

  • Найдя совпадение, создайте KeyIdObject для него и заполните его список потомков путем поиска соответствующих потомков JObject с помощью вложенного рекурсивного поиска.

  • Затем перейдите к следующему брату матчей во внешнем рекурсивном поиске.

Это может быть достигнуто путем введения метода расширения, который ищет самых верхних потомков данного JToken которые соответствуют заданному условию:

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

Затем вы можете использовать его для создания рекурсивного List<KeyIdObject> вот так:

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

Демонстрационная скрипка №1 здесь, которая генерирует следующую структуру:

[
  {
    "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
      }
    ]
  }
]

Тем не менее, в вашем JSON некоторые из ваших объектных узлов, в частности, "Clubs" а также "Subs", не имеют T_id имущество. Таким образом, они не могут быть включены в иерархию узлов, так как нет возможности заполнить long T_id значение. Если вам нужно захватить эти узлы, вы можете изменить модель данных, чтобы иметь значение NULL для идентификатора, и захватить промежуточные узлы следующим образом:

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

Демо скрипка № 2 здесь.

Наконец, если вы уверены, что ваши ключи уникальны на любом заданном уровне, вы можете использовать словарь вместо списка, например так:

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

Демо скрипка № 3 здесь.

Обратите внимание, что во всех случаях я выбрал long вместо int для T_id для безопасности.


Обновить

Если вы собираетесь связать это в WPF TreeView или что-то подобное, как Syncfusion.Xamarin.SfTreeView, вы захотите реализовать INotifyPropertyChanged и использовать ObservableCollection<T>, Вы также можете использовать другой ItemTemplate для узлов с и без T_id значения, в этом случае вы можете определить разные C# POCO для каждого случая. Ниже приведен один пример:

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

Который вы бы использовали следующим образом:

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

Демо скрипка № 4 здесь.

Другие вопросы по тегам