4

I'm trying to dynamically find the names of leaf nodes of a JSON object whose structure isn't known in advance. First, I'm parsing the string into a list of JTokens, like this:

        string req = @"{'creationRequestId':'A',
                        'value':{
                            'amount':1.0,
                            'currencyCode':'USD'
                        }
                        }";
        var tokens = JToken.Parse(req);

Then I'd like to identify which are leaves. In the above example, 'creationRequestId':'A', 'amount':1.0, and 'currencyCode':'USD' are the leaves, and the names are creationRequestId, amount, and currencyCode.

Attempt that works, but is slightly ugly

The below example recursively traverses the JSON tree and prints the leaf names:

    public static void PrintLeafNames(IEnumerable<JToken> tokens)
    {
        foreach (var token in tokens)
        {
            bool isLeaf = token.Children().Count() == 1 && !token.Children().First().Children().Any();
            if (token.Type == JTokenType.Property && isLeaf)
            {
                Console.WriteLine(((JProperty)token).Name);
            }

            if (token.Children().Any())
                PrintLeafNames(token.Children<JToken>());
        }
    }

This works, printing:

creationRequestId
amount
currencyCode

However, I'm wondering if there's a less ugly expression for determining whether a JToken is a leaf:

bool isLeaf = token.Children().Count() == 1 && !token.Children().First().Children().Any();

Incidentally, this is a one-liner in XML.

Community
  • 1
  • 1
Peter Richter
  • 755
  • 1
  • 6
  • 15
  • Why doesn't `!token.Children().Any()` suffice? – Preston Guillot Dec 24 '15 at 20:26
  • That's precisely what `HasValues` is for. – Jeff Mercado Dec 24 '15 at 20:28
  • @PrestonGuillot and @JeffMercado : I've tried both of those since they seemed like the obvious solution, and neither works. The reason is that (for example) on the JToken `'creationRequestId':'A'`, the expression `!token.Children().Any()` evaluates to `false`. If you run the code, you'll see what I mean: nothing gets printed. – Peter Richter Dec 24 '15 at 20:37

1 Answers1

7

It looks like you've defined a leaf as any JProperty whose value does not have any child values. You can use the HasValues property on the JToken to help make this determination:

public static void PrintLeafNames(IEnumerable<JToken> tokens)
{
    foreach (var token in tokens)
    {
        if (token.Type == JTokenType.Property)
        {
            JProperty prop = (JProperty)token;
            if (!prop.Value.HasValues)
                Console.WriteLine(prop.Name);
        }
        if (token.HasValues)
            PrintLeafNames(token.Children());
    }
}

Fiddle: https://dotnetfiddle.net/e216YS

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
  • This fails in case when value is empty object e.g. 'value': {} – Apurv Gupta Mar 28 '19 at 05:40
  • 1
    @ApurvGupta An empty object does not have any child values; therefore, `value` is a leaf according to the definition in the question. It sounds like you might have a different definition of a leaf, where you require the property value to be a primitive (as opposed to an empty object or empty array). If that is the case, you can make it work the way you want by changing the conditional `if (!prop.Value.HasValues)` to `if (prop.Value is JValue)`. – Brian Rogers Mar 30 '19 at 21:01