Search code examples
amazon-web-servicesaws-step-functions

How to transform JSON object using Amazon States Language?


Problem

I need to transform the state machine input for a Task using the keys from one object (which we'll call obj1) in the input and a separate key-value pair in the input (which we'll call obj2). Several keys in obj1 are optional parameters so I can't assume the keys that will be present a priori. Additionally, several other key-value pairs may exist that are irrelevant to the Task and so shall not be included in the payload.

I'm specifically looking for a single Step Functions Task definition that solves this problem.

Example

State machine Task input:

{
    "obj1": { "a": 1, "b": "foo" },
    "obj2": { "c": 2, "d": "bar" },
    "obj3": "baz",
    "obj4": { ... }
}

Transformed input for Step Functions Task:

{
    "payload": {
        "a": 1,
        "b": "foo",
        "obj2": { "c": 2, "d": "bar" }
    }
}

Attempts

I've been attempting to solve this problem using AWS Step Functions Intrinsic functions to no avail. States.JsonMerge won't work since I need the key "obj2" to be present in the payload object, and I don't want irrelevant key-value pairs such as "obj1", "obj3", or "obj4" to be in the payload. States.Format seemed promising using the below syntax, but it doesn't appear Amazon States Language (ASL) observes the spread operator. Not sure if it's a syntax issue or if it's simply not possible using ASL.

MyTask:
    Type: Task
    Resource: arn:aws-us-gov:states:::states:startExecution.sync:2
    Parameters:
      StateMachineArn: "${my_sfn_arn}"
      Input:
        payload.$: "States.Format({ ...$.obj1, 'obj2.$': $.obj2 })"

That Task definition failed to deploy with the following error message:

Resource handler returned message: "Invalid State Machine Definition: 'SCHEMA_VALIDATION_FAILED: The value for the field 'payload.$' must be a valid JSONPath or a valid intrinsic function call...

Note: I know this can be accomplished using a Lambda function, but this same problem exists in several Step Functions workflows so my question is only interested in an ASL-native solution for transforming the inputs.


Solution

  • Use the JsonMerge intrinsic function to "spread" obj1's keys.

    Solution 1: Exactly match the expected output

    Add a Pass state before MyTask to isolate obj2.

        "Obj2": {
          "Type": "Pass",
          "Parameters": { "obj2.$": "$.obj2" },
          "Next": "MyTask"
        },
    

    Then merge that output with obj1's keys. We reference obj1 from the execution's $$ Context object:

    Input: 
        payload.$: "States.JsonMerge($, $$.Execution.Input.obj1, false)"
    

    Solution 2: Simpler if the obj1 key can stay

    Input: 
        payload.$: "States.JsonMerge($.obj1, $, false)"
    

    Given your input, the task receives the 3 desired keys in the right shape, but keeps a redundant obj1 key:

    "payload": {
        "a": 1, ✅
        "b": "foo", ✅
        "obj1": {"a": 1, "b": "foo"},
        "obj2": {"c": 2, "d": "bar"} ✅
    }