Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Tricky business of discovering parent node in JSON.Net

Tags:

json

json.net

this is further to my question here

JSON.NET - Confusion in getting Parent from JToken

I am still having problems in understanding how to discover the parent in JSON.NET

So Now I have this JSON Document

{
    "attributes": {"attr0":"value0"},
    "children" : {
        "ProductA" : {
            "attributes": {"attr1":"value1", "attr2":"value2"}, 
            "children" : {
                "ProductC":{
                    "attributes": {"attr3":"value3", "attr4":"value4"}, 
                    "children" : {
                        "ProductD":{
                            "attributes": {"attr7":"value7", "attr8":"value8"}, 
                            "children" : {
                                "ProductE":{
                                    "attributes": {"attr9":"value9", "attr10":"value10"}, 
                                    "children" : {}, 
                                    "referencedChildren" : {}
                                }
                            }, 
                            "referencedChildren" : {}
                        }
                    }, 
                    "referencedChildren" : {}
                }
            }, 
            "referencedChildren" : {}
        }, 
        "ProductB" : {
            "attributes": {"attr5":"value5", "attr6":"value6"}, 
            "children" : {}, 
            "referencedChildren" : {}
        }
    },
    "referencedChildren" : {}
}

Based on this I have written this code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.IO;

namespace JSonTest {
  class Program {
    static void Main(string[] args) {
      string content = File.ReadAllText(@"c:\temp\foo.txt");
      JObject token = JObject.Parse(content);
      JToken p2 = token["children"]["ProductA"]["children"]["ProductC"]["children"]["ProductD"]["children"]["ProductE"];
      JToken parent = p2;
      do {
        parent = GetParentModule(parent);
        Console.WriteLine(((JProperty)parent).Name);
      } while (parent != null);
    }

    private static JToken GetParentModule(JToken token) {
      JToken retVal = token;
      int i = 5;
      while (i > 0) {
        retVal = retVal.Parent;
        if (retVal == null) {
          break;
        } else {
          --i;
        }
      }
      return retVal;
    }
  }    
}

When I run this code, the first call to GetParentModule return "ParentD"... but the second call does not return "ParentC"

My objective here is that when I call GetParentModule on ProductE it returns ProductD and when I call GetParentModule on ProductD it returns ProductC and when I call GetParentModule on ParentC it returns ProductA.

In my previous thread I found that 5 calls to Parent, returned me the parent correctly. but in subsequent calls I see that "4" calls to Parent returns the "ProductC".

Can you please explain what is going on and how can walk up the parent hierarchy successfully?

like image 943
Knows Not Much Avatar asked Feb 03 '26 07:02

Knows Not Much


1 Answers

I think you are confused because of two factors working against you in concert:

  1. The actual in-memory hierarchy used by Json.Net does not match your mental model of the JSON.
  2. The syntax token["property"] simplifies downward traversal of the JSON by abstracting away the actual structure to better fit your mental model. But, there is no such convenience for navigating upward, so you are exposed to all the extra layers.

Let's take a simplified example of your JSON and explore what's going on in a little more detail. Say we have this JSON:

{
    "children": {
        "ProductA": {
            "children": {
                "ProductC": {
                    "attribute": "some stuff"
                }
            }
        }
    }
}

In your mental model you have this:

  • a top-level object (with no name), which contains
    • an object named "children", which contains
      • an object named "ProductA", which contains
        • an object named "children", which contains
          • an object named "ProductC", which contains
            • an attribute with a value of "some stuff".

But the Json.Net object model works like this:

  • A JObject is a collection of JProperty objects.
  • A JProperty is a name-value pair, where the name is string and the value is either a JObject, JValue or JArray.
  • The children of a JObject are its JProperties.
  • The child of a JProperty is its value.

So the actual in-memory representation of the JSON is:

  • a top-level JObject, which contains
    • a JProperty named "children" which contains
      • a JObject, which contains
        • a JProperty named "ProductA", which contains
          • a JObject, which contains
            • a JProperty named "children", which contains
              • a JObject, which contains
                • a JProperty named "ProductC", which contains
                  • a JObject, which contains
                    • a JProperty named "attribute", which contains
                      • a JValue with a string value of "some stuff".
So you can see that what you think of as a single named object is actually an unnamed JObject wrapped inside named JProperty. And this serves to make the object hierarchy twice as deep as you would expect.

When you do something like this:

JObject productA = (JObject)top["children"]["ProductA"];

these extra layers are hidden from you. It looks just as if each object (or property) is nested directly in the one above it. But don't be fooled. Under the covers, this indexer syntax is really just a shortcut for this equivalent code:

JObject productA = (JObject)top.Children<JProperty>()
                               .First(prop => prop.Name == "children")
                               .Value
                               .Children<JProperty>()
                               .First(prop => prop.Name == "ProductA")
                               .Value;

Hopefully by now it should be clear what is going on, and we can get back to your real question, which is how to go up the chain and get the desired result. For example, say we have a reference to product C and we want to get product A (or more exactly, we have a reference to the JObject that is the value of the JProperty whose name is "ProductC", and we want to go up the chain to get the value of the JProperty whose name is "ProductA"). How can we do that?

Well, again, if you look at the actual Json.Net structure, you can see a pattern to it. Each JObject that you've identified as a "product" is inside a JProperty with an "interesting" name that is not "children". And if that product has a "parent product", it will have an ancestor JProperty whose name is "children". And the parent of that JProperty is the one you want.

So in other words, all you need to do is walk upward until you find the first JProperty whose name is "children", then take the parent of that JProperty, and that should be the JObject you are looking for.

In code:

private static JToken GetParentModule(JToken token)
{
    while (token != null && 
          (token.Type != JTokenType.Property ||
          ((JProperty)token).Name != "children"))
    {
        token = token.Parent;
    }
    return (token != null ? token.Parent : null);
}
like image 159
Brian Rogers Avatar answered Feb 05 '26 07:02

Brian Rogers