Maksym Slavnenko
Maksym Slavnenko

Reputation: 45

JSON.NET. Navigate to JArray object from its children

There's a JSON file like this:

{
  "men": [
    {
      "name": "Jordan",
      "phone": "333-333-33"
    },
    {
      "name": "Timothey",
      "phone": "444-444-44"
    }
  ],
  "women": [
    {
      "name": "Jordan",
      "phone": "111-111-11"
    },
    {
      "name": "Sasha",
      "phone": "222-222-22"
    }
  ]
}

I'd like to find all people whose name starts with J and find whether this person is a man or a woman.

var jsonProps = jsonDoc.Descendants().Where(t => t.Type == JTokenType.Property && ((JProperty)t).Name == prop);
var startsWithJ = jsonProps.Where(t => ((JProperty)t).Value.ToString().StartsWith("J"));

foreach (var person in startsWithJ)
{
    Console.WriteLine(person.Value<string>());
    Console.WriteLine(person.Parent.Parent.Value<string>());
}

The issue is that person.Parent.Parent is null and I expect it ot be JArray or at least JToken or at least some JToken so I can get its value.

Update: I have like 100 types that use similar data structure. These collections (men, women) can be on any nesting level e.g. employees include men and women collection, or students include men and women collections. I can not create a strongly typed object for each of the types I have in my solution. Sometimes I need to remove objects from men/women collections and check whether the collection has any elements after this.

Upvotes: 4

Views: 2767

Answers (2)

Brian Rogers
Brian Rogers

Reputation: 129787

Your code doesn't work because you are operating on all the name properties that have a value that starts with "J", not the person objects that contain name properties that have a value that starts with "J". So when you navigate upward, you aren't going far enough. The name property's parent is the person object. The person object's parent is the array, and the array's parent is the men (or women) JProperty. (Also, to find out whether the array's containing property's name is "men" or "women", you'll need to get its Name, not its Value.)

So, if you change your code to the following, you should get the result you expect:

foreach (JProperty nameProp in startsWithJ)
{
    Console.WriteLine(nameProp.Value);
    Console.WriteLine(((JProperty)nameProp.Parent.Parent.Parent).Name);
}

Fiddle: https://dotnetfiddle.net/J7WxiF

Personally, I find it a little easier to work at the JObject level rather than the JProperty level. Here is another way to get the same result:

var people = jsonDoc["men"]
    .Children<JObject>()
    .Union(jsonDoc["women"].Children<JObject>())
    .Where(obj => obj["name"] != null && obj["name"].ToString().StartsWith("J"));

foreach (JObject person in people)
{
    Console.WriteLine(person["name"]);
    Console.WriteLine(((JProperty)person.Parent.Parent).Name);
}

Fiddle: https://dotnetfiddle.net/jzUOFT

If you are finding that you are always wanting to find the person's parent collection name in this way, you could go so far as to define an extension method:

public static class JHelper
{
    public static bool IsMale(this JObject person)
    {
        return ((JProperty)person.Parent.Parent).Name == "men";
    }
}

Then you can simply call IsMale() on the JObject. It makes the code a little more readable.

foreach (JObject person in people)
{
    Console.WriteLine("Name: " + person["name"]);
    Console.WriteLine("Gender: " + (person.IsMale() ? "male" : "female"));
}

Upvotes: 1

Yuval Itzchakov
Yuval Itzchakov

Reputation: 149598

This isn't a direct answer to "how you query with LINQ to Json", but an approach i find easier using a strongly typed object.

By simply creating the following data structures (note the POCO's are decorated with JsonProperty which is a Json.NET attribute):

public class RootObject
{
    public List<Person> Men { get; set; }
    public List<Person> Women { get; set; }
}

public class Person
{
    [JsonProperty("name")]
    public string Name { get; set; }

    [JsonProperty("phone")]
    public string PhoneNumber { get; set; }

    public Sex Sex { get; set }
}

public enum Sex
{
    Man,
    Women
}

Now, you can rather easily query what you need using LINQ to Objects:

var rootObject = JsonConvert.DeserializeObject<RootObject>(json);

foreach (var male in rootObject.Men)
{
    male.Sex = Sex.Man;
}

foreach (var female in rootObject.Women)
{
    female.Sex = Sex.Women;
}

var startsWithJ = rootObject.Men.Concat(rootObject.Women)
                            .Where(x => x.Name.StartsWith("J",
                                        StringComparison.OrdinalIgnoreCase))
                            .ToList();

foreach (var personStartsWithJ in startsWithJ)
{
    Console.WriteLine (personStartsWithJ.Name);
    Console.WriteLine (personStartsWithJ.Sex);
}

Upvotes: 2

Related Questions