c# - 迭代 json 输入并基于 "keys"考虑到子级 "keys"创建类似 TreeView 的层次结构

标签 c# json json.net

我有以下代码:

 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 下降层次结构,直到找到 JObjectT_id属性(property)。

  • 找到匹配项后,构造一个 KeyIdObject并通过使用嵌套递归搜索搜索匹配的 JObject 的子项来填充其子项列表。

  • 然后在外部递归搜索中继续进行匹配的下一个兄弟。

这可以通过引入一个扩展方法来实现,该方法搜索给定 JTokentopmost 后代。符合给定条件的:

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/

相关文章:

c# - 在 asp.net 中以编程方式设置 "expires"http header 的值

javascript - Angular 翻译多个 json 文件

android - picasso 的图像方向发生了变化

c#在序列化时重命名派生属性

C# ASP.NET cs0246 问题

c# - 为什么这会产生 "Empty Path Name Is Not Legal"异常?

c# - Server 2008 任务计划程序映射驱动器访问 C#

python - 如何使用 Flask 处理从 jquery 数据表发送的服务器端参数?

c# - Newtonsoft JSON for .net 忽略 jsonproperty 标签

java - 跨平台 JSON 构造型/序列化器对类型属性使用react? (Java/Objective-C)