LINQ to JSON - ошибка NullReferenceException от JToken, который может быть либо JValue, либо JArray

Я пытаюсь выбрать всех пользователей с roleId 4 на основе первого элемента массива или значения. Как я могу это сделать? Кроме того, как я могу отобразить roleId? Вот моя строка JSON:

{
    "?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"]
                }
            }
        ]
    }
}

Вот запрос, который я использую:

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
});

Я получаю следующую ошибку на ((int[]) ids.ToObject(typeof(int[]))).Any(k => k == 4):

NullReferenceException: ссылка на объект не установлена ​​на экземпляр объекта.

В итоге мои результаты должны быть: Joe Schmoe & Steve Jobs, только.

Что я делаю неправильно?

1 ответ

Решение

Вам нужно сделать

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

Скорее, чем

let ids = people["roleIds.int"]

Это потому что ids собственность вложена в roleIds свойство, а для запросов вложенных объектов, SelectToken() должен быть использован. JObject.Item(String) возвращает только свойство с таким точным именем, которое может включать ., Т.е. твой оригинал let Заявление будет работать на следующее:

{
    "roleIds.int": "4"
}

В то время как SelectToken() должен использоваться для:

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

Полный запрос:

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"]
            };

Рабочая скрипка.

Обновить

Если вы хотите добавить RoleIds как целочисленный массив для вашего возвращенного анонимного типа, вы можете сделать что-то вроде:

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"]
            };

Используя метод расширения:

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 };
    }
}

И чтобы получить первый идентификатор роли, измените select пункт к:

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

Образец скрипки.

Другие вопросы по тегам