Выполните итерацию по входу 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 здесь.