Using Dynamic Dependency in JSON Forms
  • 02 Apr 2024
  • Dark
    Light
  • PDF

Using Dynamic Dependency in JSON Forms

  • Dark
    Light
  • PDF

Article Summary

This simple use case explains how to use a dynamic device name as a dependency to dynamically provide a list of available interfaces on a Cisco Router.

Note: See JSON Forms for details on creating JSON Forms and working with Form Builder to create the form elements referenced throught this guide.

How to Build Dynamic Dependencies in JSON Forms

Follow these steps to build and configure dynamic dependencies in the JSON form. As the form is built, it can be previewed and the generated form schemas can be inspected.

Build JSON Form Structure

First, create the structure of the JSON form:

  1. Create a JSON Form using the Automation Studio application.

  2. Add two dropdown elements to the JSON form canvas using the Form Elements bin.

  3. Hover over the menu button () at the far-right of each dropdown element and click the grid icon that appears. The Configure dialog opens.

  4. Use the Label field to name the first dropdown element "Device" and the second "Interface" (Figure 1).

    Figure 1
    Figure 1

  5. Click Save () to retain the naming inputs.

Add Dynamic Data Binding (Devices)

Next, add the logic that is required to dynamically populate the fields. This is done via API calls.

  1. Hover over the menu button () and click the gear icon () to open the Configure dialog.

  2. Click the +Options button located at the bottom of the dialog and select the Dynamic configuration type. The Dynamic Data Binding editor displays.

  3. For the Request Configuration use the following parameters:

    Method:  POST
    Base URL:  /configuration_manager
    API Route: /devices
    
  4. In the Request Body editor, add options to help sort the list as well as limit the amount of data that is pulled. At a minimum, the suggested options are:

    {
      "options": {
        "limit": 10000,
        "start": 0,
        "sort": [
          {
            "name": 1,
            "address": -1,
            "port": 1
          }
        ]
      }
    }
    
  5. Once the Request Body is added, click the Make API Call button to generate the Response Body (Figure 2).

    Figure 2
    Figure 2

  6. For Data Mapping use Source-To-Target and the field inputs below.

    Source Property: /list
    Property Key: /name
    
  7. Click the Query Data button. The Data Mapping properties will generate a preview list of devices that are available and discoverable by Configuration Manager (Figure 3).

    Figure 3
    Figure 3

  8. Click the Back button once all configurations are set and then click Save to retain all changes.


Configure Additional Dynamic Binding (Interfaces)

Once the "Device" selections are set, you can then configure the dynamic options for "Interfaces".

  1. Click the + Additional Binding button at the top of the Dynamic Data Binding editor.

  2. For the Request Configuration use the following:

    Method:  GET
    Base URL:  /configuration_manager
    API Route: /devices/:name/configuration
    
  3. Populate the Define Parameters name field with an active device name found within Configuration Manager.

  4. Click the Make API Call button to return the device configuration (Response Body).

  5. For Data Mapping select JSON Transformation and input the "Transform Device Config" JST file provided at the end of this guide.

  6. Click the Run Transformation button. The Target Data Preview editor displays the interface name.

  7. For the Variables (Field Dependency), select Form Reference, and input the name of the device from the Response Body (Step 4).

    /device
    

    Figure 4
    Figure 4

  8. Click the Back button once all interface mappings are set and then click Save to retain all configuration changes.

Validate the JSON Form

To test the logic of the JSON form:

  1. Navigate to IAP → Automation Studio → JSON Forms. JSON Forms is listed in the left navbar as its own subcategory in Automation Studio.
  2. Expand the JSON Forms menu in the left navbar and select the JSON Form.
  3. Click the eye icon at the top to open the Preview Form dialog.
  4. Select a device from the Device dropdown. Likewise, select the interface captured from the device configuration in the Interface dropdown.
  5. Click Show Form Data to display the JSON schema. Click Close to return to the preview modal.

Figure 5
Figure 5

Note: The information presented within Preview Form is from a live device and may take a few seconds to display based on the size of the device configuration as well as network congestion.

JSON Transformation File for Device Config

To import, copy the JSON into a .txt file and save it as “Transform Device Config.jst.json”. Then import the file into your transformation library under Automation Studio.

{
  "name": "Transform Device Config",
  "incoming": [
    {
      "$id": "config",
      "type": "string"
    }
  ],
  "outgoing": [
    {
      "$id": "out",
      "type": "array"
    }
  ],
  "steps": [
    {
      "id": 2,
      "type": "assign",
      "from": {
        "location": "incoming",
        "name": "config",
        "ptr": ""
      },
      "to": {
        "location": "method",
        "name": 1,
        "ptr": "/args/0/value"
      },
      "context": "#"
    },
    {
      "id": 1,
      "type": "method",
      "library": "String",
      "method": "split",
      "args": [
        null,
        "\n",
        null
      ],
      "view": {
        "row": 1,
        "col": 1
      },
      "context": "#"
    },
    {
      "id": 4,
      "type": "assign",
      "from": {
        "location": "method",
        "name": 1,
        "ptr": "/return"
      },
      "to": {
        "location": "method",
        "name": 3,
        "ptr": "/args/0/value"
      },
      "context": "#"
    },
    {
      "id": 3,
      "type": "method",
      "library": "Array",
      "method": "filter",
      "args": [
        null,
        "ƒ_query_1"
      ],
      "view": {
        "row": 2,
        "col": 2
      },
      "context": "#"
    },
    {
      "id": 6,
      "type": "method",
      "library": "Array",
      "method": "map",
      "args": [
        null,
        "ƒ_map_1"
      ],
      "view": {
        "row": 1,
        "col": 3
      },
      "context": "#"
    },
    {
      "id": 7,
      "type": "assign",
      "from": {
        "location": "method",
        "name": 3,
        "ptr": "/return"
      },
      "to": {
        "location": "method",
        "name": 6,
        "ptr": "/args/0/value"
      },
      "context": "#"
    },
    {
      "id": 8,
      "type": "assign",
      "from": {
        "location": "method",
        "name": 6,
        "ptr": "/return"
      },
      "to": {
        "location": "outgoing",
        "name": "out",
        "ptr": ""
      },
      "context": "#"
    }
  ],
  "functions": [
    {
      "name": "ƒ_query_1",
      "incoming": [
        {
          "type": [
            "array",
            "boolean",
            "number",
            "integer",
            "string",
            "object",
            "null"
          ],
          "$id": "element"
        },
        {
          "title": "index",
          "type": "number",
          "optional": true,
          "$id": "index"
        },
        {
          "type": "array",
          "$id": "array",
          "optional": true
        },
        {
          "$id": "thisArg",
          "type": "object",
          "properties": {},
          "isContext": true,
          "isThis": true,
          "isIndexed": true
        }
      ],
      "outgoing": [
        {
          "title": "return",
          "type": "boolean",
          "$id": "return"
        }
      ],
      "steps": [
        {
          "id": 5,
          "type": "assign",
          "from": {
            "location": "incoming",
            "name": "element",
            "ptr": ""
          },
          "to": {
            "location": "method",
            "name": 4,
            "ptr": "/args/0/value"
          },
          "context": "#"
        },
        {
          "id": 4,
          "type": "method",
          "library": "String",
          "method": "trim",
          "args": [
            null
          ],
          "view": {
            "row": 2,
            "col": 1
          },
          "context": "#"
        },
        {
          "id": 6,
          "type": "assign",
          "from": {
            "location": "method",
            "name": 4,
            "ptr": "/return"
          },
          "to": {
            "location": "method",
            "name": 1,
            "ptr": "/args/0/value"
          },
          "context": "#"
        },
        {
          "id": 1,
          "type": "method",
          "library": "String",
          "method": "startsWith",
          "args": [
            null,
            "interface",
            null
          ],
          "view": {
            "row": 1,
            "col": 1
          },
          "context": "#"
        },
        {
          "id": 3,
          "type": "assign",
          "from": {
            "location": "method",
            "name": 1,
            "ptr": "/return"
          },
          "to": {
            "location": "outgoing",
            "name": "return",
            "ptr": ""
          },
          "context": "#"
        }
      ],
      "functions": [],
      "view": {
        "col": 1,
        "row": 3
      },
      "id": "ƒ_query_1",
      "comments": []
    },
    {
      "name": "ƒ_map_1",
      "incoming": [
        {
          "type": [
            "array",
            "boolean",
            "number",
            "integer",
            "string",
            "object",
            "null"
          ],
          "$id": "currentValue"
        },
        {
          "title": "index",
          "type": "number",
          "optional": true,
          "$id": "index"
        },
        {
          "type": "array",
          "$id": "array",
          "optional": true
        },
        {
          "$id": "thisArg",
          "type": "object",
          "properties": {},
          "isContext": true,
          "isThis": true,
          "isIndexed": true
        }
      ],
      "outgoing": [
        {
          "title": "newValue",
          "type": [
            "array",
            "boolean",
            "number",
            "integer",
            "string",
            "object",
            "null"
          ],
          "editable": true,
          "$id": "newValue"
        }
      ],
      "steps": [
        {
          "id": 1,
          "type": "method",
          "library": "String",
          "method": "trim",
          "args": [
            null
          ],
          "view": {
            "row": 1,
            "col": 1
          },
          "context": "#"
        },
        {
          "id": 2,
          "type": "assign",
          "from": {
            "location": "incoming",
            "name": "currentValue",
            "ptr": ""
          },
          "to": {
            "location": "method",
            "name": 1,
            "ptr": "/args/0/value"
          },
          "context": "#"
        },
        {
          "id": 3,
          "type": "assign",
          "from": {
            "location": "method",
            "name": 1,
            "ptr": "/return"
          },
          "to": {
            "location": "outgoing",
            "name": "newValue",
            "ptr": ""
          },
          "context": "#"
        }
      ],
      "functions": [],
      "view": {
        "col": 1,
        "row": 3
      },
      "id": "ƒ_map_1",
      "comments": []
    }
  ],
  "view": {
    "col": 3,
    "row": 5
  },
  "_id": "6472c5cb363dd75d098b8d0d",
  "description": "",
  "comments": [],
  "version": "3.23.3-2022.1.14"
}

Working with API Calls in JSON Forms with Dynamic Data Binding

When working with Dynamic Data Binding in JSON Forms, it is important to know the adapter calls do not always translate directly to the system-level API call the adapter is translating. This mainly applies to system-level GET calls.

GET/POST API Calls

As a criterion, all system-level GET calls with defined query parameters (optional or mandatory) in the adapter's OpenAPI/Swagger specification document will be treated (translated) as adapter POST calls.

To illustrate, let's refer to the F5 BIG-IP API depicted in the figure below.

Figure 6: Retrieve BIG-IP Devices
F5 BIG-IQ Adapter

At the system-level, this is a GET API call. However, in order to make it an adapter call, the API has to be a POST request that requires an object with the object keys listed. Without those parameters in the request body, the adapter call will not work nor provide any output. In this instance, the object keys can be provided as empty values:

{ "select":"", "filter":""}

Use OpenAPI Help to Access Parameters

For all system-level GET calls that need to be translated to POST calls, the required parameters that need to be provided can be found through:

https://IAP_IP-LINK_Address:PORT/rest-api

If an adapter GET call is not listed, two items need to be reviewed:

  • Validate that an adapter POST call associated with the system does not exist.

  • Validate the developer building the JSON form has authorization to make the adapter call that is being attempted.

Related Reading


Was this article helpful?

Changing your password will log you out immediately. Use the new password to log back in.
First name must have atleast 2 characters. Numbers and special characters are not allowed.
Last name must have atleast 1 characters. Numbers and special characters are not allowed.
Enter a valid email
Enter a valid password
Your profile has been successfully updated.