1

Issue

The problem I'm having is that when returning "treeNode" after building it, it doesn't serialize when sent down the wire via API. The object [and nested elements] looks great while debugging but when the browser receives the data for the tree it's converted to empty arrays (some with the correct length when there is more deeply nested child nodes). Any help is appreciated, see code and output below.

Running:

TreeNode<GenericClass> treeNode = new TreeNode<GenericClass>(new GenericClass() { Title = "Root", URL = null });
GenericClass Google = new GenericClass() { Title = "Google", URL = "https://www.google.com" };
GenericClass Yahoo = new GenericClass() { Title = "Yahoo", URL = "https://www.yahoo.com" };
GenericClass Bing = new GenericClass() { Title = "Bing", URL = "https://www.bing.com" };
treeNode.AddChild(Google);
treeNode.AddChild(Yahoo);
treeNode.AddChild(Bing);
// return treeNode.ToString(); <- Doesnt work
// return treeNode.ToList(); <- Doesnt work
// return JsonConvert.SerializeObject(treeNode); <-Doesnt work
return treeNode;

TreeNode (tree/hierarchy building class):

public class TreeNode<T> : IEnumerable<TreeNode<T>>
{
    #region Properties
    public T Data { get; set; }
    public TreeNode<T> Parent { get; set; }
    public ICollection<TreeNode<T>> Children { get; set; }
    public Boolean IsRoot
    {
        get { return Parent == null; }
    }
    public Boolean IsLeaf
    {
        get { return Children.Count == 0; }
    }
    public int Level
    {
        get
        {
            if (this.IsRoot)
            {
                return 0;
            }
            return Parent.Level + 1;
        }
    }
    #endregion

    public TreeNode(T data)
    {
        this.Data = data;
        this.Children = new LinkedList<TreeNode<T>>();
        this.ElementsIndex = new LinkedList<TreeNode<T>>();
        this.ElementsIndex.Add(this);
    }

    public TreeNode<T> AddChild(T child)
    {
        TreeNode<T> childNode = new TreeNode<T>(child) { Parent = this };
        this.Children.Add(childNode);
        this.RegisterChildForSearch(childNode);
        return childNode;
    }

    public override string ToString()
    {
        return Data != null ? Data.ToString() : "[data null]";
    }

    private ICollection<TreeNode<T>> ElementsIndex { get; set; }

    private void RegisterChildForSearch(TreeNode<T> node)
    {
        ElementsIndex.Add(node);
        if (Parent != null)
        {
            Parent.RegisterChildForSearch(node);
        } 
    }

    public TreeNode<T> FindTreeNode(Func<TreeNode<T>, bool> predicate)
    {
        return this.ElementsIndex.FirstOrDefault(predicate);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public IEnumerator<TreeNode<T>> GetEnumerator()
    {
        yield return this;
        foreach (var directChild in this.Children)
        {
            foreach (var anyChild in directChild)
            {
                yield return anyChild;
            }  
        }
    }
}

Output (before serialization):

enter image description here Output (after serialization):

enter image description here

Cody Tolene
  • 127
  • 1
  • 1
  • 10
  • How are serializing? – Jodrell Feb 20 '18 at 14:48
  • when you write `return JsonConvert.SerializeObject(treeNode); doesn't work`, what do you mean by doesn't work, is there an exception? Or, does this return a string you don't like the format of? – Jodrell Feb 20 '18 at 14:50
  • Yes it does give exception -> Newtonsoft.Json.JsonSerializationException: Self referencing loop detected with type 'TreeNode` – Cody Tolene Feb 20 '18 at 14:53

2 Answers2

2

In your IEnumerator implementation you have the line

yield return this;

That doesn't make sense to me.


The JSON.Net is returning

Newtonsoft.Json.JsonSerializationException: Self referencing loop detected

because its detected that soon .Net will be returning a StackOverflowException. How would this JSON look?


Moving beyond the scope of the original question, why shouldn't your GetEnumerator function be implemented like this?

public IEnumerator<TreeNode<T>> GetEnumerator()
{
    return this.Children.GetEnumerator()
}

From initial glance, it looks like your code is enumerating grand-children, not children.

The simplest way forward would be to debug the serialization process. (Without the self reference, that could take a while.)

Jodrell
  • 34,946
  • 5
  • 87
  • 124
  • When removing "yield return this;", "JsonConvert.Serialize(treeNode)" returns an empty array. – Cody Tolene Feb 20 '18 at 15:00
  • the first iteration "yield return this;" returns the parent node. The foreach's after the first iteration return the children and their children etc. – Cody Tolene Feb 20 '18 at 16:00
0

*Solution that worked for me

Running:

TreeNode<GenericClass> treeNode = new TreeNode<GenericClass>(new GenericClass() { Title = "Root", URL = null });
GenericClass Google = new GenericClass() { Title = "Google", URL = "https://www.google.com" };
GenericClass Yahoo = new GenericClass() { Title = "Yahoo", URL = "https://www.yahoo.com" };
GenericClass Bing = new GenericClass() { Title = "Bing", URL = "https://www.bing.com" };
treeNode.AddChild(Google);
treeNode.AddChild(Yahoo);
treeNode.AddChild(Bing);
return JsonConvert.SerializeObject(treeNode, Formatting.Indented, new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore });

TreeNode class:

public class TreeNode<T>
{
    #region Properties
    public T Data { get; set; }
    public TreeNode<T> Parent { get; set; }
    public ICollection<TreeNode<T>> Children { get; set; }
    public Boolean IsRoot
    {
        get { return Parent == null; }
    }
    public Boolean IsLeaf
    {
        get { return Children.Count == 0; }
    }
    public int Level
    {
        get
        {
            if (this.IsRoot)
            {
                return 0;
            }
            return Parent.Level + 1;
        }
    }
    #endregion

    public TreeNode(T data)
    {
        this.Data = data;
        this.Children = new LinkedList<TreeNode<T>>();
        this.ElementsIndex = new LinkedList<TreeNode<T>>();
        this.ElementsIndex.Add(this);
    }

    public TreeNode<T> AddChild(T child)
    {
        TreeNode<T> childNode = new TreeNode<T>(child) { Parent = this };
        this.Children.Add(childNode);
        this.RegisterChildForSearch(childNode);
        return childNode;
    }

    public override string ToString()
    {
        return Data != null ? Data.ToString() : "[data null]";
    }

    private ICollection<TreeNode<T>> ElementsIndex { get; set; }

    private void RegisterChildForSearch(TreeNode<T> node)
    {
        ElementsIndex.Add(node);
        if (Parent != null)
        {
            Parent.RegisterChildForSearch(node);
        } 
    }

    public TreeNode<T> FindTreeNode(Func<TreeNode<T>, bool> predicate)
    {
        return this.ElementsIndex.FirstOrDefault(predicate);
    }
}

Returns:

{
  "Data": {
    "Title": "Root",
    "URL": null
  },
  "Parent": null,
  "Children": [
    {
      "Data": {
        "Title": "Google",
        "URL": "https://www.google.com"
      },
      "Children": [],
      "IsRoot": false,
      "IsLeaf": true,
      "Level": 1
    },
    {
      "Data": {
        "Title": "Yahoo",
        "URL": "https://www.yahoo.com"
      },
      "Children": [],
      "IsRoot": false,
      "IsLeaf": true,
      "Level": 1
    },
    {
      "Data": {
        "Title": "Bing",
        "URL": "https://www.bing.com"
      },
      "Children": [],
      "IsRoot": false,
      "IsLeaf": true,
      "Level": 1
    }
  ],
  "IsRoot": true,
  "IsLeaf": false,
  "Level": 0
}
Cody Tolene
  • 127
  • 1
  • 1
  • 10