c#: read value from json using JToken - c#

I have a json and I want to get a value from a complex object. Here is my json:
{
"a1" : {
"a2" : {
"a3" : "desired_value"
}
}
}
And I want to obtain the value "desired value". I've tried to obtain it with this code:
var token = JToken.Parse(json);
string value = token.Value<string>("a1.a2.a3");
But the value string is null. What should I put in the path so I can read this only once, I mean without getting a token and then iterate trough it's childern and so?

You can use SelectToken in order to select the property using its path and then extract the value from that:
string value = token.SelectToken("a1.a2.a3").Value<string>();
SelectToken will return null if the path isn't found, so you might want to guard against that.
JToken.Value expects a key to be provided, whereas SelectToken supports both keys and paths (a key is a simple path). You are providing a path where a key is expected, which results in a value not being found.
To demonstrate the difference, you could also retrieve the value of a3 like this:
token.SelectToken("a1.a2").Value<string>("a3");
I wouldn't recommend doing it this way, but it does show how paths involve traversal but keys are simple indexers.

Related

Get JProperty from Json Path in Newtonsoft.JSON

I have a JSON path string. Let's say for example:
Property1.Item2 and source JSON of
{
Property1 {
Item1: "Value123",
Item2: "Value111"
}
}
and fetch like so
var property = loadedJson.SelectToken(jsonPath);
// property in this case would be a JValue whose value is "Value111"
I want to select the JProperty pointed to by the path. It currently grabs only the value (JValue). This seems like it would be straight forward, but I cannot find a solution.
After selecting the value token you can use Parent to navigate to the property:
var property = (JProperty)loadedJson.SelectToken(jsonPath)?.Parent;
Fiddle: https://dotnetfiddle.net/lmbEGb

Get absolute path of JSON object

I have a JSON object:
{
""settings"": {
""general"": {
""database"": { ""type"": ""PostgreSql"" }
}
}
}
The absolute path of my JSON object would look like this: settings/general/database/type
I tried to get all the keys with the first solution of this question:
IList<string> keys = parent.Properties().Select(p => p.Name).ToList();
This didn't work for me. The keys list only contained the first key settings and hasn't got the other ones.
There is a path property which shows the path of the node you are in but it doesn't show the complete path of the JSON object.
How can I get the absolute path like in my example?
In your question you are asking how to get the path to "my JSON object", but your sample JSON actually contains four objects, nested. (Each object begins with { and ends with } in the JSON.) So the path will be different depending on which JSON object you are referring to. It looks like you currently have a reference to the outermost object which you are querying to get the names of its properties. But this won't give you the descendant properties, as you've seen. What you need is to get the Path from the innermost property.
So I think we can boil your question down to this:
Given some JSON, how do I get the full path(s) to the deepest value(s) within the hierarchy (i.e. the leaf nodes)?
You can do that with this LINQ-to-JSON query:
var obj = JObject.Parse(json);
var paths = obj.DescendantsAndSelf()
.OfType<JProperty>()
.Where(jp => jp.Value is JValue)
.Select(jp => jp.Path)
.ToList();
If you only want the first one, then replace .ToList() with .FirstOrDefault().
Note that the path(s) will be returned with dots as delimiters. If you would prefer slashes, then add .Replace('.', '/') to jp.Path within the Select() method call.
Working demo here: https://dotnetfiddle.net/lFXtEE
Your parent object only has one key and that is "settings". Its value is a json object. That object has only one key and that is "general". Its value is a json object. That object has only one key and that is "database". Its value is a json object. You are using nested objects so you have to be specific about 'wanting all the keys' of which object.
The SO answer you referenced is not working because it is only giving you all the keys in your root object as a list of strings.
What you want is a recursive way to get all the "keys" (JProperty.Name).
If you have your JProperty with Name = "type", let us call it JProperty typeProp;. Then typeProp.Parent will get you the JContainer containing ""type"" : ""PostgreSql"" and typeProp.Parent.Parent will get you a JProperty with Name = "database".
So something like this might help (beware, untested):
JToken current = typeProp;
string path = "";
while (current != null)
{
path = current.Name + "/" + path;
if(current.Parent != null) current = current.Parent.Parent;
}
This will leave you with an extra slash on the end like this:
settings/general/database/type/
which you can remove with:
char[] charsToTrim = {'/'};
path.trimEnd(charsToTrim)

Parse JSON using Newtonsoft.Json.Linq.JToken.SelectToken

I am trying to parse JSON to obtain the value of the VIN number in the following snippet. The SelectToken call is returning a null value.
I tried using the same token to test on the following websites and it works there
http://jsonpath.com
https://jsonpath.curiousconcept.com
I am unable to identify what is going on here. Is it because this token is not supported in Newtownsoft.Json, or is it something else
[Test]
public void Test()
{
string responseContent =
"{\"GetAdvisorWipDetailPageResponse\":{\"AdvisorDetailPage\":{\"TaxLabel\":\"VAT\",\"Currency\":{\"Code\":\"UKL\",\"Description\":\"Sterling\",\"Symbol\":\"£\",\"Precision\":\"2\"},\"LastMileage\":\"0\",\"NetAmount\":\"52.73\",\"VATAmount\":\"10.55\",\"TotalAmount\":\"63.28\",\"VATRate\":\"20.00\",\"RTSLabourRate\":\"55.50\",\"DateOut\":\"2017-09-06\",\"TimeOut\":\"16:00\",\"CustomerName\":\"S 4133166Portor\",\"CustomerDetails\":{\"Name\":\"S 4133166Portor\",\"Address\":\"1 Alvin Street\\\\nHungerford\\\\n\\\\n\\\\n\"},\"ListSection\":[{\"Type\":\"2\",\"SectionDesc\":\"Vehicle Details\",\"Cols\":\"2\",\"DisplayRows\":\"10\",\"Rows\":\"10\",\"ListRow\":[{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"Registration:\",\"Col2\":\"PRA4133166\"}},{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"VIN:\",\"Col2\":\"4133166\"}},{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"Make:\",\"Col2\":\"Citroen\"}},{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"Model:\",\"Col2\":\"C3 Picasso\"}},{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"Colour:\"}},{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"Fuel Type:\",\"Col2\":\"Petrol\"}},{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"Last known odometer:\",\"Col2\":\"0\"}},{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"Next service due:\",\"Col2\":\"1200 or 06/07/2018\"}},{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"Registration date:\",\"Col2\":\"06/09/2017\"}},{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"MOT due date:\",\"Col2\":\"06/07/2018\"}}],\"Variant\":{\"Class\":\"CAR\",\"Desc\":\"Car\",\"Variants\":{\"Variant\":[{\"Class\":\"BIKE\",\"Desc\":\"Motorcycle\"},{\"Class\":\"CAR\",\"Desc\":\"Car\"},{\"Class\":\"COMP\",\"Desc\":\"Component vehicle\"},{\"Class\":\"HGV\",\"Desc\":\"Heavy Goods Vehicle\"},{\"Class\":\"LCV\",\"Desc\":\"Light Commercial Vehicle\"}]}}},{\"Type\":\"2\",\"SectionDesc\":\"Today's Service Details\",\"Cols\":\"2\",\"DisplayRows\":\"6\",\"Rows\":\"6\",\"ListRow\":[{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"Repair number:\",\"Col2\":\"20285\"}},{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"Account:\",\"Col2\":\"C0002 - Service Retail Cash Sales\"}},{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"Notes:\",\"Col2\":\"Carry out repair :-\"}},{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"Goods value:\",\"Col2\":\"£52.73\"}},{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"VAT value:\",\"Col2\":\"£10.55\"}},{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"Total value:\",\"Col2\":\"£63.28\"}}]},{\"Type\":\"4\",\"SectionDesc\":\"Recent Service History\"}],\"VHCCompleted\":\"0\",\"Reminders\":\"0\",\"PreviousVHC\":{\"Available\":\"0\"}},\"Version\":\"3.228\",\"Partition\":\"972\",\"Startup\":\"2017-09-06T15:40\",\"RequestsServiced\":\"1\"}}";
string key = "GetAdvisorWipDetailPageResponse.AdvisorDetailPage.ListSection[0].ListRow[*].[?(#.Col1=='VIN:')].Col2";
var content = JObject.Parse(responseContent);
var value = content.SelectToken(key).ToString();
}
The un-escaped string is here
{"GetAdvisorWipDetailPageResponse":{"AdvisorDetailPage":{"TaxLabel":"VAT","Currency":{"Code":"UKL","Description":"Sterling","Symbol":"£","Precision":"2"},"LastMileage":"0","NetAmount":"52.73","VATAmount":"10.55","TotalAmount":"63.28","VATRate":"20.00","RTSLabourRate":"55.50","DateOut":"2017-09-06","TimeOut":"16:00","CustomerName":"S 4133166Portor","CustomerDetails":{"Name":"S 4133166Portor","Address":"1 Alvin Street\\nHungerford\\n\\n\\n"},"ListSection":[{"Type":"2","SectionDesc":"Vehicle Details","Cols":"2","DisplayRows":"10","Rows":"10","ListRow":[{"WipNo":"20285","Row":{"Col1":"Registration:","Col2":"PRA4133166"}},{"WipNo":"20285","Row":{"Col1":"VIN:","Col2":"4133166"}},{"WipNo":"20285","Row":{"Col1":"Make:","Col2":"Citroen"}},{"WipNo":"20285","Row":{"Col1":"Model:","Col2":"C3 Picasso"}},{"WipNo":"20285","Row":{"Col1":"Colour:"}},{"WipNo":"20285","Row":{"Col1":"Fuel Type:","Col2":"Petrol"}},{"WipNo":"20285","Row":{"Col1":"Last known odometer:","Col2":"0"}},{"WipNo":"20285","Row":{"Col1":"Next service due:","Col2":"1200 or 06/07/2018"}},{"WipNo":"20285","Row":{"Col1":"Registration date:","Col2":"06/09/2017"}},{"WipNo":"20285","Row":{"Col1":"MOT due date:","Col2":"06/07/2018"}}],"Variant":{"Class":"CAR","Desc":"Car","Variants":{"Variant":[{"Class":"BIKE","Desc":"Motorcycle"},{"Class":"CAR","Desc":"Car"},{"Class":"COMP","Desc":"Component vehicle"},{"Class":"HGV","Desc":"Heavy Goods Vehicle"},{"Class":"LCV","Desc":"Light Commercial Vehicle"}]}}},{"Type":"2","SectionDesc":"Today's Service Details","Cols":"2","DisplayRows":"6","Rows":"6","ListRow":[{"WipNo":"20285","Row":{"Col1":"Repair number:","Col2":"20285"}},{"WipNo":"20285","Row":{"Col1":"Account:","Col2":"C0002 - Service Retail Cash Sales"}},{"WipNo":"20285","Row":{"Col1":"Notes:","Col2":"Carry out repair :-"}},{"WipNo":"20285","Row":{"Col1":"Goods value:","Col2":"£52.73"}},{"WipNo":"20285","Row":{"Col1":"VAT value:","Col2":"£10.55"}},{"WipNo":"20285","Row":{"Col1":"Total value:","Col2":"£63.28"}}]},{"Type":"4","SectionDesc":"Recent Service History"}],"VHCCompleted":"0","Reminders":"0","PreviousVHC":{"Available":"0"}},"Version":"3.228","Partition":"972","Startup":"2017-09-06T15:40","RequestsServiced":"1"}}
The following JSONPath query string works with Json.NET:
string key = "GetAdvisorWipDetailPageResponse.AdvisorDetailPage.ListSection[0].ListRow[?(#.Row.Col1=='VIN:')].Row.Col2";
And returns, as a result, 4133166.
Working .Net fiddle.
Why does this work? If I simplify the ListRow section of your JSON, it looks like this:
{
"ListRow":[
{
"WipNo":"20285",
"Row":{
"Col1":"Registration:",
"Col2":"PRA4133166"
}
},
{
"WipNo":"20285",
"Row":{
"Col1":"VIN:",
"Col2":"4133166"
}
}
]
}
You are looking for an entry in the ListRow array for which Row.Col1 has a certain value, specifically "VIN:". When found, you want to select the value of Row.Col2. The JSONPath query string
ListRow[?(#.Row.Col1=='VIN:')].Row.Col2
Does this.
(In your query, you are trying to apply a filter to objects nested directly inside objects (specifically, filtering the value of Col inside the Row object inside the ListRow array item object). This is apparently not implemented in Json.NET as of 10.0.3; see Json.NET JSONPath query not returning expected results for further discussion.)

JSON.net parent key

I'm having a hard time getting the parent key/property/attribute of my JSON objects using JSON.net. That is, I want the outermost property name, as a string, without knowing beforehand/visually what it is. I'm currently iterating over a set of KeyValuePair items and attempting to, for each of those, log out the parent from something that looks like
{"parentKey":
{
"Name": "Name",
"Id": "123",
"Other": null,
"nestedArr":
[
"idx0",
"idx1",
"idx2"
]
}
}
I've tried both keyValue.Value.Ancestors() and keyValue.Value.Parent. With the former, I'm getting what looks to be the function definition... I'm actually not sure what it is: Newtonsoft.Json.Linq.JToken+<GetAncestors>d_ _ 42. Completely beffuddled by that, because based on the usage examples I've scrounged up here, I'm using it to standard.
With the latter, I log out the entire object, or else what appears to be the entire preceding KeyValuePair, rather than just the string "parentKey", which is what I want. The JSON.net docs aren't the best as far as explicit usage examples and what to expect (or maybe it's just that being new to C#, I can't make sense of them), but in any case, I'm kind of unclear on why this is happening and how to accomplish what I want. This is what I'm trying:
foreach (var keyValue in jObjList[0]) //jObjList is a List<JObject> defined above
{
Console.WriteLine(keyValue.Value.Ancestors());
Console.WriteLine(keyValue.Value.Parent);
if (keyValue.Value.GetType() == typeof(JObject))//same block goes for if it's typeof(JArray)
{
Console.WriteLine(keyValue.Key);
}
}
Edit: in the JSON given, and within the loop defined above, for example, in order to get my parent keys (that's just what I'm calling them), my code simply says, if (keyValue.Value.GetType() == typeof(JObject), write keyValue.Key to the console, and the same goes for if getType() is a JArray. In either case, keyValue.Key is a parent key, if that makes sense. What I mean to say by this is that it is a property that points to another Array or Object. My issue is that, as I'm doing this loop recursively, when I get down to a nested Array or Object, my code has no way of realizing that, although there is a new "parent key" currently, like with nestedArr, for example, the parent key of nestedArr is still "parentKey".
the code is abridged, but that's the idea.
All clarifications and corrections are welcome and appreciated. Thanks.
You are seeing Newtonsoft.Json.Linq.JToken+<GetAncestors>d_ _ 42 for Console.WriteLine(keyValue.Value.Ancestors()) because Ancestors is an IEnumerable<T> whose evaluation is lazy, rather than an explicit collection. What you are seeing is the ToString() output of the not-yet-evaluated enumerable.
If what you want to do is to climb up the parent list of a given JToken and find the lowest parent that has a "parentKey" property, then get the value of that parentKey, then this is how you would do it:
JToken token = keyValue.Value; // Here I'm declaring JToken explicitly for clarity. Normally I would use var token = ...
var parentKey = token.AncestorsAndSelf() // Climb up the json container parent/child hierachy
.Select(p => p.SelectToken("parentKey")) // Get the "parentKey" property in the current parent (if present)
.FirstOrDefault(k => k != null); // Return the first one found.
Console.WriteLine(parentKey);
Update
To get the name of the JSON property highest in the JSON container hierarchy, you would do:
var name = token.AncestorsAndSelf() // Walk up the list of ancestors
.OfType<JProperty>() // For each that is a property
.Select(p => p.Name) // Select the name
.LastOrDefault(); // And return the last (topmost).
Update 2
If you're looking for the first property name that appears in a JSON file, you can do the following, using JContainer.DescendantsAndSelf():
var json = #"[{""parentKey"":
{
""Name"": ""Name"",
""Id"": ""123"",
""Other"": null,
""nestedArr"":
[
""idx0"",
""idx1"",
""idx2""
]
}
}]";
var root = (JContainer)JToken.Parse(json);
var name = root.DescendantsAndSelf() // Loop through tokens in or under the root container, in document order.
.OfType<JProperty>() // For those which are properties
.Select(p => p.Name) // Select the name
.FirstOrDefault(); // And take the first.
Debug.WriteLine(name); // Prints "parentKey"
(JContainer represents a JSON node that can contain child nodes, such as an object or array.)

Modifying the string list using reflection

Say, I have a class User which has (string FirstName, List siblings)
I want to modify the properties of the user.
Let's assume that I want to replace the strings with b instead of a.
user : {
firstname: "Rager",
siblings : {
"stalin",
"Marx"
} }
using reflection I need to read the individual strings and the following would be the output object.
user : {
firstname: "Rbger",
siblings : {
"stblin",
"Mbrx"
} }
Let's consider the below function
private object modifyObject(object t){
foreach(var propertyInfo in t.GetType.GetProperties(){
var stringToBeModified = propertyInfo.GetValue(t,null);
propertyInfo.SetValue(t, stringToBeModified.replace("a","b"),null)
}
}
The above code works fine when modifying the firstName. But dont know how to modify the strings in siblings.
I thought I will make use of the 3rd property (optional index value for the indexed properties). But it looks like the whole property was not indexed.
for siblings, propertyInfo.GetValue(t,null) gives 2 strings.
[0] -- stalin
[1] -- Marx.
Can anyone tell me how I can modify the above 2 strings after getting the value using propertyInfo.GetValue(t,null)?
You can simply cast the value as List<string> and update as desired
eg
List<string> list = (List<string>)propertyInfo.GetValue(t,null);
list[0] = list[0].replace("a","b");
above sample is assuming the propertyInfo of siblings which is of type List<string>, you may adjust as needed.

Categories