0

I wanted to parse the JSON with Newtonsoft.Json.Linq into to a tree format by going to each root level.

The actual problem i am facing is the content inside the allOf is not getting printed and its getting InvalidCast exception in JObject. I need help to print all the parent and child elements in the console application.

Here is the JSON:

{
  "properties": {
    "displayName": "Audit if Key Vault has no virtual network rules",
    "policyType": "Custom",
    "mode": "Indexed",
    "description": "Audits Key Vault vaults if they do not have virtual network service endpoints set up. More information on virtual network service endpoints in Key Vault is available here: _https://learn.microsoft.com/en-us/azure/key-vault/key-vault-overview-vnet-service-endpoints",
    "metadata": {
      "category": "Key Vault",
      "createdBy": "",
      "createdOn": "",
      "updatedBy": "",
      "updatedOn": ""
    },
    "parameters": {},
    "policyRule": {
      "if": {
        "allOf": [
          {
            "field": "type",
            "equals": "Microsoft.KeyVault/vaults"
          },
          {
            "anyOf": [
              {
                "field": "Microsoft.KeyVault/vaults/networkAcls.virtualNetworkRules[*].id",
                "exists": "false"
              },
              {
                "field": "Microsoft.KeyVault/vaults/networkAcls.virtualNetworkRules[*].id",
                "notLike": "*"
              },
              {
                "field": "Microsoft.KeyVault/vaults/networkAcls.defaultAction",
                "equals": "Allow"
              }
            ]
          }
        ]
      },
      "then": {
        "effect": "audit"
      }
    }
  },
  "id": "/subscriptions/xxxxxx/providers/Microsoft.Authorization/policyDefinitions/wkpolicydef",
  "type": "Microsoft.Authorization/policyDefinitions",
  "name": "xyz"
}

And my code:

static JmesPath jmes = new JmesPath();
static void Main(string[] args)
    {
        string policyStr = "JSON GIVEN IN THE DESCRIPTION";
        string str = jmes.Transform(policyStr, "properties.policyRule.if");
        Convert(str);
    }

    public static void Convert(string json)
    {
        dynamic myObj = JsonConvert.DeserializeObject(json);
        PrintObject(myObj, 0);

        Console.ReadKey();
    }

    private static void PrintObject(JToken token, int depth)
    {
        if (token is JProperty)
        {
            var jProp = (JProperty)token;
            var spacer = string.Join("", Enumerable.Range(0, depth).Select(_ => "\t"));
            var val = jProp.Value is JValue ? ((JValue)jProp.Value).Value : "-";

            Console.WriteLine($"{spacer}{jProp.Name}  -> {val}");


            foreach (var child in jProp.Children())
            {
                PrintObject(child, depth + 1);
            }
        }
        else if (token is JObject)
        {
            foreach (var child in ((JObject)token).Children())
            {
                PrintObject(child, depth + 1);
            }
        }
    }

I have installed JMESPath.Net NuGet package. Demo fiddle here.

dbc
  • 104,963
  • 20
  • 228
  • 340
Kailash P
  • 71
  • 1
  • 9
  • What problem are you experiencing? – user247702 Jun 12 '19 at 16:43
  • @Stijn I needed to form a tree in c# but not able to do it till the child level i have followed the below link but its not accessing all the elements its excluding few elements https://stackoverflow.com/questions/45837283/c-sharp-parsing-relaxed-json-to-make-a-tree – Kailash P Jun 12 '19 at 16:58
  • Please add the code that you use to form the tree. It's not possible to tell you what part of your code is wrong without seeing it. – user247702 Jun 12 '19 at 16:59
  • @Stijn i have added the code for reference – Kailash P Jun 12 '19 at 17:17
  • @dbc i have added the code for reference – Kailash P Jun 12 '19 at 17:17
  • @KailashP - your code doesn't compile, see https://dotnetfiddle.net/jOUtJe: *Compilation error (line 24, col 10): The type or namespace name 'JmesPath' could not be found*. Can you share the JSON string being passed into `Convert`? – dbc Jun 12 '19 at 17:26
  • However this may help: [How to do recursive descent of json using json.net?](https://stackoverflow.com/a/16208023). – dbc Jun 12 '19 at 17:28
  • @dbc i have installed JMESPath.Net NuGet package – Kailash P Jun 12 '19 at 17:48

1 Answers1

2

Your basic problem is that, in PrintObject(JToken token, int depth), you do not consider the case of the incoming token being a JArray:

if (token is JProperty)
{
}
else if (token is JObject)
{
}
// Else JArray, JConstructor, ... ?

Since the value of "allOf" is an array, your code does nothing:

{
  "allOf": [ /* Contents omitted */ ]
}

A minimal fix would be to check for JContainer instead of JObject, however, this does not handle the case of an array containing primitive values, and so cannot be considered a proper fix. (Demo fiddle #1 here.)

Instead, in your recursive code you need to handle all possible subclasses of JContainer including JObject, JArray, JProperty and (maybe) JConstructor. However, the inconsistency between JObject, which has two levels of hierarchy, and JArray which has only one, can make writing such recursive code annoying.

One possible solution to processing arrays and objects in a cleaner manner would be to hide the existence of JProperty entirely and represent that objects are containers whose children are indexed by name while arrays are containers whose children are indexed by integers. The following extension method does this job:

public interface IJTokenWorker
{
    bool ProcessToken<TConvertible>(JContainer parent, TConvertible index, JToken current, int depth) where TConvertible : IConvertible;
}

public static partial class JsonExtensions
{
    public static void WalkTokens(this JToken root, IJTokenWorker worker, bool includeSelf = false)
    {
        if (worker == null)
            throw new ArgumentNullException();
        DoWalkTokens<int>(null, -1, root, worker, 0, includeSelf);
    }

    static void DoWalkTokens<TConvertible>(JContainer parent, TConvertible index, JToken current, IJTokenWorker worker, int depth, bool includeSelf) where TConvertible : IConvertible
    {
        if (current == null)
            return;
        if (includeSelf)
        {
            if (!worker.ProcessToken(parent, index, current, depth))
                return;
        }
        var currentAsContainer = current as JContainer;
        if (currentAsContainer != null)
        {
            IList<JToken> currentAsList = currentAsContainer; // JContainer implements IList<JToken> explicitly
            for (int i = 0; i < currentAsList.Count; i++)
            {
                var child = currentAsList[i];
                if (child is JProperty)
                {
                    DoWalkTokens(currentAsContainer, ((JProperty)child).Name, ((JProperty)child).Value, worker, depth+1, true);
                }
                else
                {
                    DoWalkTokens(currentAsContainer, i, child, worker, depth+1, true);
                }
            }
        }
    }
}

Then your Convert() method now becomes:

class JTokenPrinter : IJTokenWorker
{
    public bool ProcessToken<TConvertible>(JContainer parent, TConvertible index, JToken current, int depth) where TConvertible : IConvertible
    {
        var spacer = new String('\t', depth);
        string name;
        string val;

        if (parent != null && index is int)
            name = string.Format("[{0}]", index);
        else if (parent != null && index != null)
            name = index.ToString();
        else 
            name = "";

        if (current is JValue)
            val = ((JValue)current).ToString();
        else if (current is JConstructor)
            val = "new " + ((JConstructor)current).Name;
        else
            val = "-";

        Console.WriteLine(string.Format("{0}{1}   -> {2}", spacer, name, val));
        return true;
    }
}

public static void Convert(string json)
{
    var root = JsonConvert.DeserializeObject<JToken>(json);
    root.WalkTokens(new JTokenPrinter());
}

Demo fiddle #2 here, which outputs:

allOf   -> -
    [0]   -> -
        field   -> type
        equals   -> Microsoft.KeyVault/vaults
    [1]   -> -
        anyOf   -> -
            [0]   -> -
                field   -> Microsoft.KeyVault/vaults/networkAcls.virtualNetworkRules[*].id
                exists   -> false
            [1]   -> -
                field   -> Microsoft.KeyVault/vaults/networkAcls.virtualNetworkRules[*].id
                notLike   -> *
            [2]   -> -
                field   -> Microsoft.KeyVault/vaults/networkAcls.defaultAction
                equals   -> Allow

Related:

dbc
  • 104,963
  • 20
  • 228
  • 340