I’m trying to extract values from json using Newtonsoft, but I’m having some problems.
Below is the json snippet I use:
{
"result": {
"areas": [
{
"area": 1,
"items": [
{
"name": "name1",
"numbers": [
2,
2,
2,
1
],
"value": 0.3
},
{
"name": "name2",
"numbers": [
0,
1,
0,
1
],
"value": 0.6
},
{
"name": "name3",
"numbers": [
-2,
3,
1,
1
],
"value": 0.5
}
]
},
{
"area": 2,
"items": [
{
"name": "name4",
"numbers": [
1,
2,
3,
4
],
"value": 0.7
}
]
}
]
}
}
My task is to first filter by area (I need data only from area 1) and then select those items that meet the conditions for numbers:
- 1st number = 2 or = -2,
- 2nd number > 1,
- 3rd number > 0,
- 4th number = 1,
and then calculate the average value for these selected items.
In this case, I want to select items 1 and 3, i.e. to get an average of 0.4.
I first tried to do it in one filter with multiple conditions:
IEnumerable<JToken> numbers = jo.SelectTokens("$...items[?( (@.numbers[0] == 2 || @.numbers[0] == -2 ) && @.numbers[1] > 1 && @.numbers[2] > 0 && @.numbers[3] == 1)].value");
I got an error in this case because I can’t use additional brackets in the query, and I don’t know how else to check all the conditions.
Then I wrote each query separately and connected them using the Intersect() function:
IEnumerable<JToken> numbers0 = jo.SelectTokens("$...items[?(@.numbers[0] == 2 || @.numbers[0] == -2)]");
IEnumerable<JToken> numbers1 = jo.SelectTokens("$...items[?(@.numbers[1] > 1)]");
IEnumerable<JToken> numbers2 = jo.SelectTokens("$...items[?(@.numbers[2] > 0)]");
IEnumerable<JToken> numbers3 = jo.SelectTokens("$...items[?(@.numbers[3] == 1)]");
IEnumerable<JToken> numbers = numbers0.Intersect(numbers1.Intersect(numbers2.Intersect(numbers3)));
In this case, however, I am not able to extract the value and the entire json fragment is returned to me.
In both cases, I am unable to include the area filtering condition, the returned token is empty:
IEnumerable<JToken> area = jo.SelectTokens("$..areas[?( @.area == 1)]");
Am I doing something wrong? Maybe this can be fixed somehow without changing the approach.
And if this is not possible, what technique do you recommend? I’ve read a bit about LINQ and will try that next if the current task is unsolvable, unless you think there’s something even better.
2
Answers
You are correct that
SelectTokens()
does not support parenthesized combinations of AND and OR, see How can I create a JSONPath filter expression containing both AND and OR operators? for confirmation. Thus the three possible workarounds from this answer for filtering the"$...items[*]"
items should work for you as well. Once you have filtered the items as required, you can append a.Select(i => i["value"])
to your query to select the value you need.Specifically, you have the following three ways to work around Json.NET’s limitation and select the values you need:
You can rely on Json.NET’s undocumented behavior that
A && B || C
meansA && (B || C):
No added
Select()
is required with this approach.You can use
Intersect()
as you are doing now, with an addedSelect()
:You could use LINQ to filter your items instead of JSONPath, with the added
select
:This approach is, in my opinion, the most readable.
Once you have your values, you can average them by casting each selected
JToken
to adouble?
like soDemo fiddle showing all three approaches here.
I will try to solve this sentence by sentence with minimal coding.
1/. "I’m trying to extract values from json using Newtonsoft"
-> https://www.newtonsoft.com/json/help/html/deserializeobject.htm
2/. How to generate class from Json
https://app.quicktype.io
After those step: the code boils down to : live demo
3/. "filter by area 1"
result.Result.Areas.Where(x=> x.Id == 1)
4/. Valid item based on Numbers indexes and value.
5/. Use it to validation to get Item 1 and 3.
var validItems = area.Items.Where(IsItemValid);
6/. Average the value.
var avg = validItems.Average(x=> x.Value);
final result : livedemo