Search code examples
pathjson.netparentjsonpath

Json.Net - Immediate parent path not being returned


I am attempting to retrieve the path of the immediate parent of a JToken object found via SelectToken.

  • grandparent
    • parent
      • object

In above structure the value of object.Path is "grandparent.parent.object" and the value of object.Parent.Path is also "grandparent.parent.object".

Is this a bug or should the path of a parent be retrieved in another way?

Below is an example that illustrates object.Path and object.Parent.Path being the same:

var input = "{'grandparent': { 'parent' : {'object' : 'value'}}}";

var jsonInput = JObject.Parse(input);
var jsonObject = jsonInput.SelectToken("..object");

var path = jsonObject.Path; //grandparent.parent.object
var parentPath = jsonObject.Parent.Path; //grandparent.parent.object (same as object)
var realParentPath = jsonObject.Parent.Parent.Path; //grandparent.parent (actual parent path)

Solution

  • You have stumbled on an implementation detail of Json.NET, which is that it models a JSON object with two levels of container, namely the JObject which contains a collection of JProperty items, each of which in turn contains the actual property value:

    JObject                // A JSON object: an unordered set of name/value pairs
     -> IEnumerable<JProperty> Properties()
        JProperty          // A property name/value pair
         -> string Name    // The property name
         -> JToken Value   // The property value
    

    I.e., using the diagram for an object from https://json.org/:

    JSON object

    The JObject corresponds to the entire section between braces, and the JProperty corresponds to a specific string : value portion.

    I reckon this implementation was chosen to separate the name from the value, so that JValue could be used for both array and object primitive values, without having to add in a meaningless Name property for array items. However, from the point of view of SelectToken, the existence of JProperty is a bit awkward because it doesn't correspond to anything selectable via a JSONPath query since SelectToken always returns the actual value rather than the container property. Newtonsoft chose to make JProperty.Path the same as it's value's path; possibly they could have chosen to make JProperty.Path throw an exception instead, but they did not.

    To hide this implementation detail, you could introduce an extension method SelectableParent():

    public static partial class JsonExtensions
    {
        public static JToken SelectableParent(this JToken token)
        {
            if (token == null)
                return null;
            var parent = token.Parent;
            if (parent is JProperty)
                parent = parent.Parent;
            return parent;
        }
    }
    

    Then use it as follows:

    var path = jsonObject.Path; //grandparent.parent.object
    var parentPath = jsonObject.SelectableParent().Path; //grandparent.parent
    

    Demo fiddle here.

    Related: Why does AddAfterSelf return 'JProperty cannot have multiple values' when used with SelectToken?.