Search code examples
c#jsonjson.netjson-deserialization

Select JToken by property value


I am trying to find a more elegant solution than foreach loops for the following problem.

Sample JSON Structure:

[{
  "OpportunityId": "ABC-123-XYZ",
  "Status": "Active",
  "Milestones": {
    "Milestone": [
      {
        "Name": "Award",
        "Date": "8\/27\/2021"
      }
    ]
  },
  "Staff": {
    "Pocs": [
      {
        "Role": "Development Lead",
        "Name": "Doe, John",
        "Email": "[email protected]"
      },
      {
        "Role": "Capture Manager",
        "Name": "Doe, Jane",
        "Email": "[email protected]"
      }
    ]
  }
}]

I can get the data I want using the following:

string json = [see above];
JArray? obj = JsonConvert.DeserializeObject<JArray>(json);

foreach (var single in obj)
{
    JToken? pocs = single.Value<JToken>("Staff")?.Value<JToken>("Pocs");
    if (pocs != null)
    {
       foreach(var poc in pocs)
       {
           if (poc.Value<string>("Role") == "Capture Manager")
           {
               [Do something with the resulting JToken/values]
           }
       }
    }
}

I feel like there has to be some Linq to help me here, but everything I've tried thus far has been unsuccessful. I have no control over the JSON input, I just need to deal with it as is. Any help would be greatly appreciated!


Solution

  • As mentioned by @gunr2171 in the comment, you can work with JSON Path.

    using Newtonsoft.Json.Linq;
    using System.Collections.Generic;
    
    JArray jArray = JArray.Parse(json);
    IEnumerable<JToken> jTokens = jArray.SelectTokens("$..Staff.Pocs[?(@.Role == 'Capture Manager')]");
    

    The above JSON Path:

    $ - From root element.

    ..Staff - Deep scan the element node with the Staff field.

    .Pocs - Access to the dot-notated Pocs child.

    [?(<expression>)] - Filter expression.

    @.Role == 'Capture Manager' - Current node with the filter expression that matches the Role field value equal to "Capture Manager".

    Reference:

    1. Querying JSON with complex JSON Path
    2. Cheatsheet for JSON Path

    Working with System.Linq and Newtonsoft.Json.Linq.

    using Newtonsoft.Json.Linq;
    using System.Collections.Generic;
    using System.Linq;
    
    IEnumerable<JToken> jTokens = jArray.Children()
                    .Values("Staff")
                    .SelectMany(x => x["Pocs"])
                    .Where(x => x.SelectToken("Role").Value<string>() == "Capture Manager")
                    .ToList()