【问题标题】:How to determine if a JSON object contains only a specific key?如何确定 JSON 对象是否仅包含特定键?
【发布时间】:2019-03-18 17:24:48
【问题描述】:

我有一个 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. Determining whether a JSON object contains a specific fieldname in jQuery(我的问题是关于 C#,而不是 jQuery,这与对象中是否存在“字段名”无关。而是关于键是否是 only存在于对象中)。

【问题讨论】:

  • 为什么投反对票?如何改进问题?
  • @dbc - 谢谢。我已经编辑了这个问题以满足您的建议。现在可以了吗?
  • @Sipo - 好吧,我不明白为什么有人会认为这是这些问题的重复,因为语言不同。但是,这样肯定更好。
  • 所以如果我猜对了,您的结果将是名称为“身份”、“地址”和“银行账户”的类别?
  • @dbc - 谢谢。我经常发生这种情况,人们只阅读了标题并匆忙将问题标记为重复。由于我们不应该在标题中放置标签,因此我没有在标题中指定这个问题是关于 C# 的。这就是为什么我首先将便笺放在顶部的原因,这样人们就不会无缘无故地将其标记为重复。

标签: c# json linq json.net except


【解决方案1】:

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

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

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

小提琴样本here.

更新

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

根据documentationEnumerable.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 对象是 根据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),然后使用SetEquals()

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

演示小提琴#2 here

【讨论】:

  • 谢谢,这个解决方案有效,所以我赞成这个答案。但是,它对我来说似乎并不优雅,因为与 Except(...).Any() 相比,它的声明性不够。我应该选择Except(...).Any() 解决方案吗,我需要在括号中添加什么?再次感谢!
【解决方案2】:

不使用动态的替代解决方案是创建与您的 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 属性,该属性被序列化程序忽略。如果属性 Name 是唯一具有值的属性,则此属性返回 true

测试代码:

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

【讨论】:

    【解决方案3】:

    一个简单的Where 就可以了:

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

    【讨论】:

      猜你喜欢
      • 2012-10-17
      • 1970-01-01
      • 2011-11-26
      • 1970-01-01
      • 1970-01-01
      • 2015-03-15
      • 1970-01-01
      • 1970-01-01
      • 2014-02-14
      相关资源
      最近更新 更多