Search code examples
c#json.netazure-logic-apps

Recursive deserialisation of JSON


I am attempting to work with JSON that defines a Logic App Standard workflow.

Within the JSON there are "actions", these are like sequential steps in a workflow. Actions can have child actions and hence an action can have a parent action. The sequence of execution of actions is dictated by the "runAfter" property which refers to another action.

My goal is to list each action and its corresponding runAfter and parent in C# (currently experimenting with Newtonsoft.Json) for any workflow. An example workflow is below.

My lack of familiarity with Newtonsoft is the real problem as I can't yet see how to check if there are child actions and how to recursively parse them.

{
  "definition": {
    "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
    "actions": {
      "For_each_result_set": {
        "actions": {
          "For_each_(row)_active_widget": {
            "actions": {
              "Insert_row_to_acme_job_status": {
                "inputs": {
                  "parameters": {
                    "setColumns": {
                      "ClientTrackingID": "@{variables('clientTrackingId')}",
                      "WidgetId": "@{items('For_each_(row)_active_widget')?['widgetID']}"
                    },
                    "tableName": "[acme].[acme_job_status]"
                  },
                  "serviceProviderConfiguration": {
                    "connectionName": "sql-4",
                    "operationId": "insertRow",
                    "serviceProviderId": "/serviceProviders/sql"
                  }
                },
                "type": "ServiceProvider"
              },
              "Send_message_to_get-widget-responses_(topic)": {
                "inputs": {
                  "body": {
                    "Properties": {
                      "clientTrackingId": "@{variables('clientTrackingId')}",
                      "widgetId": "@{items('For_each_(row)_active_widget')?['widgetID']}"
                    }
                  },
                  "host": {
                    "connection": {
                      "referenceName": "servicebus-2"
                    }
                  },
                  "method": "post",
                  "path": "/@{encodeURIComponent(encodeURIComponent('get-widget-responses'))}/messages",
                  "queries": {
                    "systemProperties": "None"
                  }
                },
                "runAfter": {
                  "Insert_row_to_acme_job_status": [
                    "SUCCEEDED"
                  ]
                },
                "type": "ApiConnection"
              }
            },
            "foreach": "@items('For_each_result_set')",
            "type": "Foreach"
          }
        },
        "foreach": "@body('Get_Active_Widgets')?['resultSets']",
        "runAfter": {
          "Get_Active_Widgets": [
            "SUCCEEDED"
          ]
        },
        "type": "Foreach"
      },
      "Get_Active_Widgets": {
        "description": "",
        "inputs": {
          "parameters": {
            "includeEmptyResultSets": true,
            "storedProcedureName": "procname"
          },
          "serviceProviderConfiguration": {
            "connectionName": "sql-4",
            "operationId": "executeStoredProcedure",
            "serviceProviderId": "/serviceProviders/sql"
          }
        },
        "runAfter": {
          "Initialise_Client_Tracking_Id": [
            "SUCCEEDED"
          ]
        },
        "type": "ServiceProvider"
      },
      "Initialise_Client_Tracking_Id": {
        "description": "",
        "inputs": {
          "variables": [
            {
              "name": "clientTrackingId",
              "type": "string",
              "value": "@{workflow().run.name}"
            }
          ]
        },
        "runAfter": {},
        "type": "InitializeVariable"
      }
    },
    "contentVersion": "1.0.0.0",
    "outputs": {},
    "triggers": {
      "Recurrence": {
        "description": "",
        "recurrence": {
          "frequency": "Day",
          "interval": 1,
          "schedule": {
            "hours": [
              "18"
            ]
          },
          "timeZone": "GMT Standard Time"
        },
        "type": "Recurrence"
      }
    }
  },
  "kind": "Stateful"
}

Solution

  • My lack of familiarity with Newtonsoft is the real problem as I can't yet see how to check if there are child actions and how to recursively parse them.

    To check recursively, I have tried below code which looks to check recursively:

    using Newtonsoft.Json.Linq;
    public class RithLogic
    {
        public string Name { get; set; }
        public string RunAfter { get; set; }
        public string Parent { get; set; }
        public List<RithLogic> ChildActions { get; set; }
    }
    
    public class Program
    {
        public static List<RithLogic> ParseActions(JObject actions, string parentAction = null)
        {
            var pa = new List<RithLogic>();
    
            string psa = null;
    
            foreach (var a in actions.Properties())
            {
                var actname = a.Name;
                var actionDetails = (JObject)a.Value;
    
                var RithLogic = new RithLogic
                {
                    Name = actname,
                    RunAfter = GetRunAfter(actionDetails),
                    Parent = parentAction,
                    ChildActions = new List<RithLogic>()
                };
    
                if (psa != null && RithLogic.RunAfter == null)
                {
                    RithLogic.RunAfter = psa;
                }
    
                if (actionDetails.ContainsKey("actions"))
                {
                    var childActions = (JObject)actionDetails["actions"];
                    RithLogic.ChildActions.AddRange(ParseActions(childActions, actname));
                }
    
                pa.Add(RithLogic);
                psa = actname;
            }
    
            return pa;
        }
    
        public static string GetRunAfter(JObject ads)
        {
            if (ads.ContainsKey("runAfter"))
            {
                var rA = ads["runAfter"];
                if (rA is JArray)
                {
                    var runAfterArray = (JArray)rA;
                    return string.Join(",", runAfterArray);
                }
                else if (rA is JProperty)
                {
                    var rap = (JProperty)rA;
                    return rap.Value.ToString();
                }
            }
            return null;
        }
    
        public static void Main(string[] args)
        {
            string rithjson = @"yourjson";
    
            JObject wf = JObject.Parse(rithjson);
            var a = (JObject)wf["definition"]["actions"];
    
            var pa = ParseActions(a);
    
            foreach (var v in pa)
            {
                Console.WriteLine($"Action Name: {v.Name}, RunAfter Action: {v.RunAfter}, Parent Action: {v.Parent}");
    
                foreach (var cA in v.ChildActions)
                {
                    Console.WriteLine($"Child Action: {cA.Name}, RunAfter Action: {cA.RunAfter}, Parent Action: {cA.Parent}");
                }
            }
    
        }
    }
    

    Output:

    enter image description here

    If you feel this is not accurately recursing, take it as an example and change code accordingly and try to get accurate results. The above code gave me my actions after trigger.