Workflow Loop JSON Parse Error

Hi,

I’m currently working on a scenario involving a Launcher for access requests. When a user launches the launcher, a form is displayed where they provide the required inputs. Based on those inputs, the workflow processes the access request.

The workflow works as expected when the user selects a single role. However, if the user selects multiple roles, it fails at the Loop step with a JSON parse error.

I’ve attached the relevant screenshots for reference. Could you please review them and let me know the possible cause or a recommended solution?


It looks like you may be passing a list, rather than Json, and the system can’t parse it properly. Inam on my phone, so I can’t see all the details, but what is the loop expecting as the input variable? i would make sure that the input variable is correct here.

Hello Gopi. The Loop is receiving the array successfully. Your Step Input shows both selected hospital values in the input property, so I think the failure is coming from the HTTP Request action inside the Loop, not the Loop itself.

Inside the Loop, try using $.loop.loopInput for the individual item being processed in the current iteration. Since your Context field points to $.defineVariable, its properties are available through $.loop.context (ref). For example:

{
  "hospitalName": "{{$.loop.loopInput}}",
  "role": "{{$.loop.context.role}}",
  "requester": "{{$.loop.context.requester}}"
}

Now, the error:

invalid character 'C' after object key:value pair

means the JSON parser finished reading a property value and then encountered C where it expected a comma or closing brace. Please check the rendered body immediately before the value beginning with C. There may be a missing comma or extra rendered content. Also verify that all string expressions are inside quotation marks.

If any inline expression resolves to an array or object, append .JSON() so it renders as valid JSON rather than a Go-style representation like [value1 value2] (ref):

{
  "hospitals": {{$.loop.context.hospital.JSON()}}
}

Don’t put quotes around that expression when the API expects a JSON array or object.

To clarify the difference: $.loop.context.hospital is the complete hospital array from your context, while $.loop.loopInput is one hospital for the current iteration. Use loopInput when each HTTP request should process only one hospital.

Your Loop Input points to $.interactiveForm.formData.hospitalName, but your question mentions selecting multiple roles. If the workflow should process each selected role, the Loop Input would need to point to the role array instead.

If the issue continues, share the HTTP Request body configuration (sensitive values removed) and we can try to narrow it down further.

I’ve tried this no luck.. but {{$.loop.loopInput}} works fine on serial loop where it is fetching values properly but I can’t able to gather the context information in serial loop

I think the issue is related to the Serial Loop. The Serial Loop does not have a Context field, so $.loop.context is not supported there. That field is available only on the regular Loop operator, which processes its iterations in parallel.

The official Workflow Operators documentation lists Context under the regular Loop’s field table. The Serial Loop configuration does not include Context; it lists Loop Type and Loop Input for a For Loop, or the condition fields for a While Loop.

However, a Serial For Loop can reference upstream workflow data directly without using Context. In this thread, @iamnithesh said that a direct reference such as $.interactiveForm.formData.ActualTechNameOfTheForm resolved successfully from inside a Serial For Loop. Tagging @iamnithesh here in case he has any additional insight into this pattern.

Inside your Serial Loop, I would suggest keeping $.loop.loopInput for the current hospital, and replacing any $.loop.context.* references with direct paths to your Define Variable step:

$.defineVariable.role
$.defineVariable.requester
$.defineVariable.identity

Make sure defineVariable matches the actual technical name of that step in your workflow builder and that these properties exist in its output.

Your latest screenshots show Get Role Details succeeding and Add Access failing, so check the Add Access configuration for any remaining $.loop.context.* references and swap them to the corresponding direct $.defineVariable.* paths.

If the Variable Selector does not list the upstream Define Variable properties, try entering the JSONPath manually using inline {{ }} syntax and verify the rendered value in the Step Input.

This one helps! additonally im facing an issue with the same workflow..

We’re currently routing all access requests through a Launcher using Workflow Forms. The form consists of two primary fields:

  • Access
  • Hospital (configured as an entitlement backed by a Flat File connector)

Our requirement is to maintain all access values selected by the user/manager in a separate Flat File account. The challenge I’m facing is that the current update operation always overwrites the existing value instead of appending to it.

For example:

  • First request: Access = A → Flat File is updated correctly.
  • Second request: Access = B → The existing value A is overwritten, resulting in B.
  • Expected behavior: The existing values should be preserved and the new value should be appended.

Example:

  • Existing Access: [A, B]
  • New Access Request: C
  • Expected Result: [A, B, C]

The complication here is that the existing access values are returned as an array, whereas the newly selected access from the form is a string. Because of this string/array combination, I haven’t been able to append the value successfully. I also tried using a Define Variable step with concatenation, but it didn’t produce the expected result.

I’m also facing another issue in the same workflow.

Before provisioning, I’m validating whether the processing identity already has an account in a specific source. Based on the result:

  • If the account exists, I perform a PATCH operation.
  • If the account doesn’t exist, I create a new account.

Since this validation is happening inside a Loop, the logic isn’t behaving as expected.

For example, assume the source already contains the following accounts:


A
B
C

During each loop iteration, I’m comparing the account’s email with:


$.getIdentity.attributes.email

If the first account (A) matches, I know the account already exists. However, the loop continues to evaluate B and C. Since those comparisons don’t match the current identity, the workflow incorrectly assumes the account doesn’t exist and attempts to execute the Create Account operation, even though the account already exists in the source.

Ideally, once a matching account is found, the workflow should stop further validation for the remaining accounts and proceed with the PATCH operation instead of continuing through the loop and triggering an unnecessary account creation.

Have you come across a similar scenario before, or do you have any suggestions or workarounds for handling either of these issues? Any guidance would be greatly appreciated. Thanks!

@Gopi2000

This issue can be tricky to identify, but the fix is usually straightforward.

In SailPoint workflows, when a step returns multiple records, the output is typically returned as an array and enclosed within square brackets ([ ]). However, when only a single record is returned, the output may be returned as an object instead of an array, without the square brackets. This inconsistency is a common cause of JSON parsing errors in Loop steps.

You’ll need to identify which workflow step is producing the output used by the Loop. Check whether that step is returning:

  • A single object when only one role is selected
  • An array of objects when multiple roles are selected

To avoid failures, ensure the output is always formatted as an array before passing it to the Loop step. If necessary, add logic to wrap a single object in square brackets ([ ]) so that the Loop always receives a consistent array structure.

In my experience, this is one of the most common challenges when working with SailPoint workflow loops. Review the output of the step preceding the Loop and verify whether the data structure changes between single-role and multi-role selections. That should help pinpoint the root cause.

Hi @vikaspawar
As you mentioned, the loopInput is returning a single object, and there is no issue with that part—it is working as expected.

The actual issue lies in the comparison logic. Inside the loop, I’m comparing the email from getIdentity with the email of each account returned from the source.

For example, let’s assume there are 10 accounts in the source. Since I’m iterating through all of them, the loop processes all 10 accounts.

  • On the first iteration, if the account email matches getIdentity.attributes.email, I know the account already exists for that identity, so I can perform the Update/PATCH operation. This part works correctly.
  • However, the loop continues to process the remaining 9 accounts. Since their email addresses don’t match getIdentity.attributes.email, the comparison evaluates to false, and the workflow enters the Create Account path for each of those iterations.

As a result, even though the account already exists in the source, the workflow still attempts to create a new account because the comparison fails for the remaining accounts.

To summarize, my requirement is:

  • If the identity already has an account in the source, update the existing account.
  • If the identity does not have an account in the source, create a new account.

The problem is that the decision to create an account is currently being made within the loop. Since the comparison fails for every non-matching account, all those iterations enter the Create Account step, even if a matching account was found earlier in the loop.

Ideally, once a matching account is found, the loop should stop further processing (or set a flag indicating that the account exists) so that the Create Account step is skipped entirely. Otherwise, the workflow should create the account only after confirming that none of the accounts in the source match the current identity.

@Gopi2000
To resolve this issue, you can check the number of records returned using the <response>.count() function. If the count is 0 (or less than 1), proceed with the account creation step. Otherwise, skip the creation process since the account already exists.

The current workflow logic is as follows:

  • Loop → Retrieves all accounts from the source.
  • Inside the Loop
    • Compare the account email from the source with the current requested identity’s email.
    • If the comparison is true → The identity already has an account, so I perform an Update/PATCH operation.
    • If the comparison is false → I proceed with the Create Account operation.

The issue is with the comparison being performed during every loop iteration.

For example, assume the source contains the following accounts:


A
B
C
D
E

Now, let’s say the requested identity is E.

The loop executes five times:

  • Iteration 1: Compare A == EFalse → Attempts to Create Account :cross_mark:
  • Iteration 2: Compare B == EFalse → Attempts to Create Account :cross_mark:
  • Iteration 3: Compare C == EFalse → Attempts to Create Account :cross_mark:
  • Iteration 4: Compare D == EFalse → Attempts to Create Account :cross_mark:
  • Iteration 5: Compare E == ETrue → Performs Update/PATCH :white_check_mark:

The problem is that the workflow attempts to create an account during the first four iterations, even though the account already exists in the source. The Create Account step should not be triggered until all accounts have been checked and no match is found.

Now consider a new identity F, which does not have an account in the source:

  • F == A → False
  • F == B → False
  • F == C → False
  • F == D → False
  • F == E → False

Since no matching account is found after checking all existing accounts, Create Account is the correct action in this scenario.

Expected Behavior

  • If any account in the source matches the requested identity, the workflow should only perform the Update/PATCH operation and never execute Create Account.
  • The Create Account operation should be executed only after the loop completes and confirms that no matching account exists. Currently, the Create Account decision is being made within each loop iteration, which causes incorrect account creation attempts for existing identities.