您想要做的是将深度嵌套的 JSON 映射到 C# 树中,其中每个节点都有两个属性 -string key
and a long T_id
——以及相同类型的子代的集合。
您可以使用列表对其进行如下建模:
public partial class KeyIdObject
{
public string key { get; set; }
public long T_id { get; set; }
public List<KeyIdObject> Children { get; set; }
}
有了数据模型后,您需要使用递归算法来生成节点。相关算法如图在 JObject 层次结构中按名称搜索特定的 JToken https://stackoverflow.com/q/19645501/3744182,但是您需要两阶段递归:
这可以通过引入搜索扩展方法来实现topmost给定的后代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();
}
}
演示小提琴#1here https://dotnetfiddle.net/KCwAi6,生成以下结构:
[
{
"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"
and "Subs"
,没有T_id
财产。因此,它们无法被捕获到节点层次结构中,因为无法填充long T_id
价值。如果您确实需要捕获这些节点,您可以修改数据模型,使 id 具有可为空的值,并捕获中间节点,如下所示:
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();
}
}
演示小提琴#2here https://dotnetfiddle.net/XtQUzD.
最后,如果您确定您的键在任何给定级别都是唯一的,您可以使用字典而不是列表,如下所示:
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) });
}
}
演示小提琴 #3here https://dotnetfiddle.net/4x1VkW.
请注意,在所有情况下,我都选择long
代替int
为了T_id
为了安全。
Update
如果您要将其绑定到 WPFTreeView https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.treeview或类似的东西Syncfusion.Xamarin.SfTreeView https://help.syncfusion.com/xamarin/sftreeview/getting-started,你会想要实现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
演示小提琴#4here https://dotnetfiddle.net/Pc3wYf.