C# 反序列化json树结构并设置父级

C# 反序列化json树结构并设置父级,c#,json,serialization,json.net,deserialization,C#,Json,Serialization,Json.net,Deserialization,这似乎是一个非常基本的问题,但我正在努力寻找一个优雅的解决方案。我有一个节点类,用来构建树结构。然后使用JsonConvert.SerializeObject(..)将其序列化为JSON。为了防止序列化时循环引用,我在父属性上放置了一个JsonIgnore属性 这显然意味着在生成的JSON输出中,父节点没有被序列化为每个节点的一部分 当我反序列化同一个JSON字符串时,我希望节点对象具有正确的父对象,以便我可以轻松地向上遍历树。实现这一目标的最干净、最简单的方法是什么 [JsonObject]

这似乎是一个非常基本的问题,但我正在努力寻找一个优雅的解决方案。我有一个节点类,用来构建树结构。然后使用
JsonConvert.SerializeObject(..)
将其序列化为JSON。为了防止序列化时循环引用,我在父属性上放置了一个
JsonIgnore
属性

这显然意味着在生成的JSON输出中,父节点没有被序列化为每个节点的一部分

当我反序列化同一个JSON字符串时,我希望节点对象具有正确的父对象,以便我可以轻松地向上遍历树。实现这一目标的最干净、最简单的方法是什么

[JsonObject]
public class Node : IEnumerable<Node>
{
    public Guid Id { get; set; }
    public string Name { get; set; }

    [JsonIgnore]
    public Node Parent { get; private set; }

    [JsonProperty("Children")]
    private readonly Dictionary<Guid, Node> _children = new Dictionary<Guid, Node>();

    public Node()
    {
        Id = Guid.NewGuid();
    }

    public void Add(Node departmentNode)
    {
        if (node.Parent != null)
        {
            node.Parent._children.Remove(node.Id);
        }

        node.Parent = this;
        _children.Add(node.Id, node);
    }

    public IEnumerator<Node> GetEnumerator()
    {
        return _children.Values.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}
[JsonObject]
公共类节点:IEnumerable
{
公共Guid Id{get;set;}
公共字符串名称{get;set;}
[JsonIgnore]
公共节点父节点{get;private set;}
[JsonProperty(“儿童”)]
专用只读词典_children=新词典();
公共节点()
{
Id=Guid.NewGuid();
}
公共作废添加(节点部门节点)
{
如果(node.Parent!=null)
{
node.Parent.\u children.Remove(node.Id);
}
node.Parent=this;
_添加(node.Id,node);
}
公共IEnumerator GetEnumerator()
{
返回_children.Values.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
返回GetEnumerator();
}
}

您可以完全摆脱父级,在需要查找时使用类似于
FindParent(node.Id)
的东西


如果这不可行(但应该如此)您需要一个父引用。我的建议是在反序列化后遍历树并设置父引用。

我所做的是忽略父引用的序列化,并实现一个名为ChildrenDict的公共属性,该属性设置我的私有ChildrenDict收集当我将子项添加到私有词典时,我还设置了每个子项的Parent属性

就我个人而言,我不喜欢用JSON特定的属性污染我的数据类,因为我喜欢独立于序列化程序的干净设计

这么说来,最终解决方案不使用JsonIgnoreAttribute标记并定义:

  • 一个私有的无参数构造函数,由JSON反序列化程序使用
  • 私有的父属性(被JSON序列化程序忽略)
  • 公共GetParent()方法(供您自己使用)
  • 一个公共构造函数,以父级作为参数(供您自己使用)
还可以定义一个SetParent()方法,尽管在我的代码中我根本不需要它

此代码使用NewtonsoftJson序列化和dotnet4.5.2进行了测试

using System.Collections.Generic;
using System.Linq;

namespace JsonSerializableNode
{
    public class Node
    {
        private Node() { } // used for deserializing

        public Node(string name, Node parent) // used everywhere else in your code
        {
            Name = name;
            Parent = parent;
        }

        public string Name { get; set; }

        private Node Parent { get; set; }

        public Node GetParent()
        {
            return Parent;
        }

        public Node[] Children
        {
            get
            {
                return ChildrenDict.Values.ToArray();
            }

            set
            {
                ChildrenDict.Clear();
                if (value == null || value.Count <= 0) return;
                foreach (Node child in value)
                    Add(child);
            }
        }

        // One could use a typed OrderedDictionary here, since Json lists guarantee the order of the children:
        private Dictionary<string, Node> ChildrenDict { get; } = new Dictionary<string, Node>();

        public Node Add(Node child)
        {
            ChildrenDict.Add(child.Name, child);
            child.Parent = this;
            return child;
        }

        public Node Get(string name)
        {
            return ChildrenDict[name];
        }

        public bool Remove(string name)
        {
            return ChildrenDict.Remove(name);
        }
    }
}
使用System.Collections.Generic;
使用System.Linq;
命名空间JsonSerializableNode
{
公共类节点
{
专用节点(){}//用于反序列化
公共节点(字符串名称,节点父节点)//在代码中的其他地方使用
{
名称=名称;
父母=父母;
}
公共字符串名称{get;set;}
私有节点父节点{get;set;}
公共节点GetParent()
{
返回父母;
}
公共节点[]子节点
{
得到
{
返回ChildrenDict.Values.ToArray();
}
设置
{
ChildrenDict.Clear();

如果(value==null | | value.Count是的,那就行了。FindParent(node.Id)的缺点方法是我总是需要使用根部门作为起点。有时我可能只想将一个子部门作为参数传递给一个方法,并能够轻松地向上遍历树。我目前有两个建议的工作版本,并将对它们进行测试。谢谢!