Michael Haddad
Michael Haddad

Reputation: 4435

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

I have a JSON object. I want to determine whether or not it contains only a specific key. Here is the code:

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

I want to determine for each category whether or not it consists of the Name key only. Using this answer, I believe the solution should look something like this:

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

But what should exactly go in the Except() method? Is there a better way of doing this? Thanks.


Note: This is not a duplicate of the following questions:

  1. How to determine if a Javascript object has only one specific key-value pair? (my question is about C#, not JavaScript).

  2. Determining whether a JSON object contains a specific fieldname in jQuery (my question is about C#, not jQuery, and it's not about whether a "fieldname" exists in an object. It's about whether a key is the only one that exists in an object).

Upvotes: 2

Views: 5061

Answers (3)

dbc
dbc

Reputation: 116741

You can always just count the properties of a JObject using JContainer.Count. You want objects with one property, named "Name":

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

The fact that you're using dynamic makes it a little harder to access the c# properties of your category JObject, since there might also be a JSON property named "Count". I'd suggest switching to explicitly typed objects and methods instead. Doing so will also give you compile-time error checking as well as possibly improved performance, as explained in How does having a dynamic variable affect performance?.

Sample fiddle here.

Update

Should I go for the Except(...).Any() solution, what would I need to put in the parenthesis?

According to the documentation, Enumerable.Except

Produces the set difference of two sequences.

So you could try something like:

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

However, there is a problem with this approach: using Except() does not meet your stated requirement that you need to

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

Except(...).Any() will test whether there are additional keys other than Name, but it will not test for the presence of the Name key itself, since it will get filtered out. Thus an empty JSON object {} would incorrectly get accepted.

Instead, you would need to check for sequence equality, e.g. like so:

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

And if you were requiring the presence of multiple keys, since a JSON object is an unordered set of name/value pairs according to the standard, you could sort them to require an unordered sequence equality:

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

Or if you prefer you could use requiredKeys.ToHashSet(StringComparer.Ordinal) and then SetEquals().

But, in my opinion, if you just need to check whether a JSON object consists of a single specified key only, the initial solution is simplest and most declarative.

Demo fiddle #2 here.

Upvotes: 3

Rui Jarimba
Rui Jarimba

Reputation: 17994

An alternative solution without using dynamics would be creating C# models that match your JSON structure and then using LINQ to filter the categories you need.

The model:

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

Please note that I added the Category.HasNameOnly property, which is ignored by the serializer. This property returns true if property Name is the only property with a value.

Testing code:

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

Upvotes: 1

Xiaoy312
Xiaoy312

Reputation: 14477

A simple Where will do:

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

Upvotes: 0

Related Questions