我有以下代码:
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 时创建这个层次结构。
有没有一种简单的方法可以做到这一点?我真的迷路了,希望能帮助我取得进步。
最佳答案
您要做的是将深度嵌套的 JSON 映射到 C# 树中,其中每个节点都有两个属性 -- string key
和一个 long T_id
-- 以及相同类型的 child 的集合。
您可以使用列表对其进行建模,如下所示:
public partial class KeyIdObject
{
public string key { get; set; }
public long T_id { get; set; }
public List<KeyIdObject> Children { get; set; }
}
一旦有了数据模型,就需要使用递归算法来生成节点。相关算法见 Searching for a specific JToken by name in a JObject hierarchy ,但你需要一个两阶段递归:
通过
JToken
下降层次结构,直到找到JObject
用T_id
属性(property)。找到匹配项后,构造一个
KeyIdObject
并通过使用嵌套递归搜索搜索匹配的 JObject 的子项来填充其子项列表。然后在外部递归搜索中继续进行匹配的下一个兄弟。
这可以通过引入一个扩展方法来实现,该方法搜索给定 JToken
的 topmost 后代。符合给定条件的:
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();
}
}
演示 fiddle #1 here ,它生成以下结构:
[
{
"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
属性(property)。因此,它们无法被捕获到节点层次结构中,因为无法填充 long T_id
。值(value)。如果您确实需要捕获这些节点,您可以修改您的数据模型以具有可为空的 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();
}
}
演示 fiddle #2 here .
最后,如果您确定您的 key 在任何给定级别都是唯一的,您可以使用字典而不是列表,如下所示:
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) });
}
}
演示 fiddle #3 here .
请注意,在所有情况下,我都选择了 long
而不是 int
对于 T_id
为了安全。
更新
如果您要将其绑定(bind)到 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
演示 fiddle #4 here .
关于c# - 迭代 json 输入并基于 "keys"考虑到子级 "keys"创建类似 TreeView 的层次结构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56383331/