Как определить, содержит ли объект JSON только определенный ключ?

У меня есть объект JSON. Я хочу определить, содержит ли он только конкретный ключ. Вот код:

{
    "Name": "ThirdParty",
    "Categories": [
        { "Name": "Identity" },
        {
            "Name": "Contact Information",
            "Mandatory": true,
            "Fields": [
                { "Name": "Phones" },
                { "Name": "Faxes" },
                { "Name": "Emails" }
            ]
        },
        { "Name": "Addresses" },
        { "Name": "Bank Accounts" }
    ]
}

Я хочу определить для каждой категории, состоит ли только из Name . Используя этот ответ , я считаю, что решение должно выглядеть примерно так:

foreach(dynamic category in jObject.Categories)
{
    var result = category.Children().Except(/* What goes here? */).Any();
}

Но что именно следует в методе Except() ? Есть ли лучший способ сделать это? Благодарю.


Примечание. Это не дубликат следующих вопросов:

  1. Как определить, имеет ли объект Javascript только одну конкретную пару «ключ-значение»? (мой вопрос касается C #, а не JavaScript).

  2. Определение того, содержит ли объект JSON определенное имя поля в jQuery (мой вопрос касается C #, а не jQuery, и дело не в том, существует ли «имя_файла» в объекте. Речь идет о том, является ли ключ единственным, который существует в объекте) ,

Всего 3 ответа


Вы всегда можете просто подсчитать свойства объекта JObject с помощью JContainer.Count . Вы хотите объекты с одним свойством с именем "Name" :

foreach (var category in jObject["Categories"].OfType<JObject>())
{
    var result = category.Count == 1 && category.Property("Name") != null;
}

Тот факт, что вы используете dynamic , немного затрудняет доступ к свойствам c # вашей категории JObject , так как может существовать свойство JSON с именем "Count" . Я бы предложил переключиться на явно типизированные объекты и методы. Это также даст вам возможность проверки ошибок во время компиляции, а также, возможно, повышенную производительность, как описано в разделе Как влияет динамическая переменная на производительность? ,

Пример скрипки здесь .

Обновить

Должен ли я пойти на Except(...).Any() решение, что мне нужно вставить в скобки?

Согласно документации , Enumerable.Except

Производит множество разностей двух последовательностей.

Поэтому вы можете попробовать что-то вроде:

    var result = !category.Properties()
        .Select(p => p.Name)
        .Except(new [] { "Name" })
        .Any();

Тем не менее, существует проблема с этим подходом: использование Except() не соответствует вашим заявленным требованиям, что вам нужно

... определять для каждой категории, состоит ли только из Name .

Except(...).Any() будет проверять наличие дополнительных ключей, отличных от Name , но он не будет проверять наличие самого Name , поскольку он будет отфильтрован. Таким образом, пустой объект JSON {} будет неправильно принят.

Вместо этого вам нужно будет проверить равенство последовательности, например:

foreach (var category in jObject["Categories"].OfType<JObject>())
{
    var result = category.Properties()
        .Select(p => p.Name)
        .SequenceEqual(new [] { "Name" });
}

И если вам требовалось присутствие нескольких ключей, поскольку объект JSON является неупорядоченным набором пар имя / значение в соответствии со стандартом , вы можете отсортировать их, чтобы потребовать неупорядоченное равенство последовательности:

var requiredKeys = new [] { "Name" } // Add additional required keys here
    .OrderBy(n => n, StringComparer.Ordinal).ToArray();
foreach (var category in jObject["Categories"].OfType<JObject>())
{
    var result = category.Properties()
        .Select(p => p.Name)
        .OrderBy(n => n, StringComparer.Ordinal)
        .SequenceEqual(requiredKeys);
}

Или, если вы предпочитаете, вы могли бы использовать requiredKeys.ToHashSet(StringComparer.Ordinal) а затем SetEquals() .

Но, на мой взгляд, если вам просто нужно проверить, состоит ли только объект JSON только с одним указанным ключом, первоначальное решение является самым простым и наиболее декларативным.

Демо-скрипка № 2 здесь .


Альтернативным решением без использования динамики было бы создание моделей C #, соответствующих вашей структуре JSON, а затем используя LINQ для фильтрации нужных категорий.

Модель:

public class Data
{
    public string Name { get; set; }
    public List<Category> Categories { get; set; }
}

public class Category
{
    [JsonIgnore]
    public bool HasNameOnly
    {
        get
        {
            return !string.IsNullOrEmpty(Name)
                   && !Mandatory.HasValue
                   && (Fields == null || !Fields.Any());
        }
    }

    public string Name { get; set; }
    public bool? Mandatory { get; set; }
    public List<Field> Fields { get; set; }
}

public class Field
{
    public string Name { get; set; }
}

Обратите внимание, что я добавил свойство Category.HasNameOnly , которое игнорируется сериализатором. Это свойство возвращает true если свойство Name является единственным свойством со значением.

Код тестирования:

string json = @"{
    ""Name"": ""ThirdParty"",
    ""Categories"": [
        { ""Name"": ""Identity"" },
        {
            ""Name"": ""Contact Information"",
            ""Mandatory"": true,
            ""Fields"": [
                { ""Name"": ""Phones"" },
                { ""Name"": ""Faxes"" },
                { ""Name"": ""Emails"" }
            ]
        },
        { ""Name"": ""Addresses"" },
        { ""Name"": ""Bank Accounts"" }
    ]
}";

Data data = JsonConvert.DeserializeObject<Data>(json);

List<Category> categories = data.Categories.Where(x => x.HasNameOnly).ToList();

Простой Where будет:

var categoriesWithMoreThanJustName = JObject.Parse(json)["Categories"]
    .Where(category => category.Values<JProperty>().Any(property => property.Name != "Name"));