C# анализирует правило политики Azure json для создания дерева

Я хотел проанализировать JSON с помощью Newtonsoft.Json.Linq в формате дерева, перейдя на каждый корневой уровень.

Фактическая проблема, с которой я сталкиваюсь, заключается в том, что содержимое внутри allOf не печатается и получает исключение InvalidCast в JObject. Мне нужна помощь, чтобы распечатать все родительские и дочерние элементы в консольном приложении.

Вот 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"
}

И мой код:

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

Я установил пакет JMESPath.Net NuGet. Демонстрационная скрипта здесь.


person Kailash P    schedule 12.06.2019    source источник
comment
Какую проблему вы испытываете?   -  person user247702    schedule 12.06.2019
comment
@Stijn Мне нужно было сформировать дерево в С#, но я не смог сделать это до дочернего уровня, я перешел по ссылке ниже, но не получил доступ ко всем элементам, за исключением нескольких элементов stackoverflow.com/questions/45837283/   -  person Kailash P    schedule 12.06.2019
comment
Пожалуйста, добавьте код, который вы используете для формирования дерева. Невозможно сказать вам, какая часть вашего кода неверна, не видя ее.   -  person user247702    schedule 12.06.2019
comment
@Stijn я добавил код для справки   -  person Kailash P    schedule 12.06.2019
comment
@dbc я добавил код для справки   -  person Kailash P    schedule 12.06.2019
comment
@KailashP - ваш код не компилируется, см. dotnetfiddle.net/jOUtJe: Ошибка компиляции (строка 24, столбец 10): имя типа или пространства имен «JmesPath» не найдено. Можете ли вы поделиться строкой JSON, передаваемой в Convert?   -  person dbc    schedule 12.06.2019
comment
Однако это может помочь: Как выполнить рекурсивный спуск json с помощью json.net?.   -  person dbc    schedule 12.06.2019
comment
@dbc Я установил пакет JMESPath.Net NuGet   -  person Kailash P    schedule 12.06.2019


Ответы (1)


Ваша основная проблема заключается в том, что в PrintObject(JToken token, int depth) вы не рассматриваете случай, когда входящий token является JArray:

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

Поскольку значение "allOf" является массивом, ваш код ничего не делает:

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

Минимальным решением будет проверка JContainer вместо JObject, однако , это не относится к массиву, содержащему примитивные значения, и поэтому не может считаться правильным исправлением. (Демонстрационная скрипта №1 здесь.)

Вместо этого в вашем рекурсивном коде вам нужно обрабатывать все возможные подклассы JContainer, включая JObject, JArray, JProperty и (возможно) JConstructor. Однако несоответствие между JObject, имеющим два уровня иерархии, и JArray, имеющим только один, может раздражать написание такого рекурсивного кода.

Одним из возможных решений более аккуратной обработки массивов и объектов было бы полное скрытие существования JProperty и представление объектов как контейнеров, дочерние элементы которых индексируются по имени, а массивы — это контейнеры, дочерние элементы которых индексируются целыми числами. Эту работу выполняет следующий метод расширения:

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

Тогда ваш метод Convert() теперь становится:

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

Демонстрационная скрипта № 2 здесь, которая выводит:

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

Связанный:

person dbc    schedule 12.06.2019