简体   繁体   中英

c# parsing azure policy rule json to make a tree

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://docs.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 .

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:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM