如何确定 JSON 对象是否仅包含特定键?

How to determine if a JSON object contains only a specific key?

我有一个 JSON 对象。我想确定它是否只包含一个特定的键。这是代码:

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

我想确定每个类别是否仅包含 Name 键。使用 this answer,我认为解决方案应该如下所示:

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

但是 Except() 方法中究竟应该包含什么?有更好的方法吗?谢谢。


注意:这不是以下问题的重复:

  1. How to determine if a Javascript object has only one specific key-value pair? (我的问题是关于 C#,而不是 JavaScript)。

  2. (我的问题是关于C#的,不是jQuery,也不是对象中是否存在"fieldname",而是key是否存在是唯一存在于对象中的)。

一个简单的Where就可以了:

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

您始终可以使用 JContainer.Count 计算 JObject 的属性。你想要一个 属性 的对象,命名为 "Name":

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

您使用 dynamic 的事实使得访问类别 JObject 的 c# 属性变得有点困难,因为可能还有 JSON 属性 命名为 "Count"。我建议改用显式类型的对象和方法。这样做还会为您提供编译时错误检查以及可能提高的性能,如 How does having a dynamic variable affect performance?.

中所述

示例 fiddle here.

更新

我应该选择 Except(...).Any() 解决方案吗,我需要在括号中输入什么?

根据 documentationEnumerable.Except

Produces the set difference of two sequences.

所以你可以尝试这样的事情:

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

但是,这种方法存在一个问题:使用 Except() 不符合您声明的要求,您需要

... determine for each category whether or not it consists of the Name key only.

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 对象是 无序的 name/value 对 集合 standard,您可以对它们进行排序以要求无序序列相等:

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) and then SetEquals()

但是,在我看来,如果您只需要检查一个 JSON 对象是否只包含一个指定的键,初始解决方案是最简单和最声明的。

演示 fiddle #2 here

另一种不使用动力学的替代解决方案是创建与您的 JSON 结构相匹配的 C# 模型,然后使用 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 属性,它被序列化程序忽略了。这个属性returnstrue如果属性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();