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

时间:2018-10-13 20:45:27

标签: c# json linq json.net except

我有一个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 一个对象中存在的一个)。

3 个答案:

答案 0 :(得分:3)

您总是可以使用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" });
}

如果您需要多个键,那么根据standard,由于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 here

答案 1 :(得分:1)

不使用动力学的替代解决方案是创建与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属性,该属性被序列化程序忽略。如果属性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();

答案 2 :(得分:0)

一个简单的Where可以做到:

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