I am attempting to retrieve the path of the immediate parent of a JToken object found via SelectToken.
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)
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/:
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?.