0

I'm trying to select all users with a roleId of 4 based on the first element of the array or value. How can I do that? Also, how can I display the roleId? Here's my JSON string:

{
    "?xml" : {
        "@version" : "1.0",
        "@encoding" : "UTF-8"
    },
    "DataFeed" : {
        "@FeedName" : "AdminData",
        "People" : [{
                "id" : "63",
                "active": "1",
                "firstName" : "Joe",
                "lastName" : "Schmoe",
                "roleIds" : {
                    "int" : "4"
                }
            } , {
                "id" : "65",
                "active": "1",
                "firstName" : "Steve",
                "lastName" : "Jobs",
                "roleIds" : {
                    "int" : ["4", "16", "25", "20", "21", "22", "17", "23", "18"]
                }
            } , {
                "id" : "66",
                "active": "1",
                "firstName" : "Bill",
                "lastName" : "Gates",
                "roleIds" : {
                    "int" : ["3", "16", "25", "20"]
                }
            }
        ]
    }
}

Here's the query that I'm using:

JObject jsonFeed = JObject.Parse(jsonString);

from people in jsonFeed.SelectTokens("DataFeed.People").SelectMany(i => i)
let ids = people["roleIds.int"]
where (int) people["active"] == 1 &&
    (ids.Type == JTokenType.Array) ?
        ((int[]) ids.ToObject(typeof(int[]))).Any(k => k == 4) : 
// <-- this looks through all items in the array.  
// I just want to compare against the 1st element
        (int) ids == 4
select new {
    Id = (int) people["id"],
    ResAnFName = (string) people["firstName"],
    ResAnLName = (string) people["lastName"],
    RoleId = ?? // <-- how do I get the roleID displayed
});

I'm getting the following error on ((int[]) ids.ToObject(typeof(int[]))).Any(k => k == 4):

NullReferenceException: Object reference not set to an instance of an object.

In the end, my results should be: Joe Schmoe & Steve Jobs, only.

What am I doing wrong?

inquisitive_one
  • 1,363
  • 6
  • 29
  • 55
  • My mistake. Yes, `json` is the same as `jsonFeed`. I've updated the question. Thx for the catch. – inquisitive_one Sep 21 '16 at 18:05
  • 1
    Possible duplicate of [What is a NullReferenceException, and how do I fix it?](http://stackoverflow.com/questions/4660142/what-is-a-nullreferenceexception-and-how-do-i-fix-it) – Igor Sep 21 '16 at 18:10
  • When you get a NRE, you need to check your variables because one of them is null when you thought it wouldn't be. Then you figure out WHY it is null. Either you're screwing up, or it's just a possible outcome. If the first, fix it. If the last, handle it. –  Sep 21 '16 at 19:39

1 Answers1

2

You need to do

let ids = people.SelectToken("roleIds.int")

Rather than

let ids = people["roleIds.int"]

That's because the ids property is nested inside the roleIds property, and for queries of nested objects, SelectToken() should be used. JObject.Item(String) only returns a property with that exact name -- which might include a .. I.e. your original let statement would work on the following:

{
    "roleIds.int": "4"
}

While SelectToken() must be used for:

{
    "roleIds" : {
        "int" : "4"
    }
}

The full query:

var query = from people in jsonFeed.SelectTokens("DataFeed.People")
                   .SelectMany(i => i)
            let ids = people.SelectToken("roleIds.int")
            where (int)people["active"] == 1 &&
                (ids.Type == JTokenType.Array) ?
                    ((int[])ids.ToObject(typeof(int[]))).Any(k => k == 4) :
                    (int)ids == 4
            select new
            {
                Id = (int)people["id"],
                ResAnFName = (string)people["firstName"],
                ResAnLName = (string)people["lastName"]
            };

Working fiddle.

Update

If you want to add RoleIds as an integer array to your returned anonymous type, you could do something like:

int desiredRoleId = 4;
var query = from people in jsonFeed.SelectTokens("DataFeed.People")
                   .SelectMany(i => i)
            let ids = people
                .SelectToken("roleIds.int")
                .SingleOrMultiple()
                .Select(t => (int)t)
                .ToArray()
            where (int?)people["active"] == 1 && ids.Contains(desiredRoleId)
            select new
            {
                Id = (int)people["id"],
                RoleIds = ids,
                ResAnFName = (string)people["firstName"],
                ResAnLName = (string)people["lastName"]
            };

Using the extension method:

public static class JsonExtensions
{
    public static IEnumerable<JToken> SingleOrMultiple(this JToken source)
    {
        if (source == null)
            return Enumerable.Empty<JToken>();
        IEnumerable<JToken> arr = source as JArray;
        return arr ?? new[] { source };
    }
}

And to get the first role ID, change the select clause to:

            select new
            {
                Id = (int)people["id"],
                FirstRoleId = ids.FirstOrDefault(),
                ResAnFName = (string)people["firstName"],
                ResAnLName = (string)people["lastName"]
            };

Sample fiddle.

dbc
  • 80,875
  • 15
  • 141
  • 235