AWS JSON API Patterns: IAM Policies, Lambda, DynamoDB & EventBridge
Last updated:
AWS services communicate almost entirely through JSON — IAM policies, Lambda event payloads, DynamoDB AttributeValue format, API Gateway mapping templates, Step Functions state machines, and EventBridge rules all use JSON as their primary configuration and data interchange format. A Lambda function invoked by API Gateway receives a JSON event with 23 top-level fields including httpMethod, path, headers, queryStringParameters, and body (base64-encoded or plain string); returning a JSON object with statusCode, headers, and body is mandatory or API Gateway returns a 502. This guide covers IAM policy JSON structure, Lambda event/context shapes, DynamoDB item marshalling, API Gateway request/response JSON, Step Functions ASL JSON, and EventBridge pattern JSON for content-based filtering.
IAM Policy JSON: Statement, Action, Resource, Condition
An IAM policy document is a JSON object with Version (always "2012-10-17") and a Statement array. Each statement specifies Effect ("Allow" or "Deny"), Action (AWS API actions), Resource (ARNs), and an optional Condition block for fine-grained control. Deny always overrides Allow regardless of statement order. The 6,144-character limit per managed policy requires careful Action and Resource grouping; use multiple policies attached to a role when needed.
// Minimal IAM policy — allow S3 read on a specific bucket
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowS3Read",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*"
]
}
]
}
// Condition operators — restrict by IP, tag, and ARN pattern
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyOutsideVPC",
"Effect": "Deny",
"Action": "s3:*",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:SourceVpc": "vpc-0abc1234"
}
}
},
{
"Sid": "AllowTaggedResources",
"Effect": "Allow",
"Action": "ec2:*",
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:ResourceTag/Environment": "production"
}
}
},
{
"Sid": "AllowLambdaInRegion",
"Effect": "Allow",
"Action": "lambda:InvokeFunction",
"Resource": "arn:aws:lambda:us-east-1:123456789012:function:*",
"Condition": {
"ArnLike": {
"aws:SourceArn": "arn:aws:events:us-east-1:123456789012:rule/*"
}
}
}
]
}
// Validate policy JSON before deploying
// aws iam validate-policy --policy-document file://policy.json --policy-type IDENTITY
// Resource-based policy (S3 bucket policy) — adds Principal field
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/MyLambdaRole"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-bucket/*"
}
]
}Use aws iam validate-policy --policy-document file://policy.json --policy-type IDENTITY to lint policy JSON before deployment — it catches structural errors and warns about redundant statements. Condition keys support prefix matching: StringLike with wildcards (* and ?) is useful for matching ARN patterns without hardcoding account IDs. The aws:PrincipalTag condition key enables attribute-based access control (ABAC), allowing policy logic driven by IAM principal tags rather than hardcoded ARNs.
Lambda JSON Event and Context Shapes
A Lambda function's handler receives two arguments: the event JSON object (structure varies by trigger source) and a context object with runtime metadata. For API Gateway proxy integration, the event shape differs between payload format v1 (REST API) and v2 (HTTP API). Returning the wrong JSON shape from a Lambda function causes API Gateway to return a 502 error to the caller.
// ── API Gateway v1 (REST API) event shape ─────────────────────
{
"httpMethod": "POST",
"path": "/users/123",
"headers": { "Content-Type": "application/json", "Authorization": "Bearer token" },
"queryStringParameters": { "include": "orders" },
"multiValueQueryStringParameters": { "tag": ["admin", "editor"] },
"pathParameters": { "userId": "123" },
"stageVariables": { "env": "prod" },
"requestContext": {
"accountId": "123456789012",
"resourceId": "abc123",
"stage": "prod",
"requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
"identity": { "sourceIp": "1.2.3.4", "userAgent": "Mozilla/5.0" }
},
"body": "{"name":"Alice"}", // always a string — JSON.parse() it
"isBase64Encoded": false
}
// ── API Gateway v2 (HTTP API) event shape ──────────────────────
{
"version": "2.0",
"routeKey": "POST /users/{userId}",
"rawPath": "/users/123",
"rawQueryString": "include=orders",
"cookies": ["session=abc123"],
"headers": { "content-type": "application/json" },
"queryStringParameters": { "include": "orders" },
"pathParameters": { "userId": "123" },
"requestContext": {
"accountId": "123456789012",
"apiId": "api1234",
"http": { "method": "POST", "path": "/users/123", "protocol": "HTTP/1.1", "sourceIp": "1.2.3.4" },
"requestId": "JKJaXmPLvHcESHA=",
"routeKey": "POST /users/{userId}",
"stage": "$default",
"time": "12/Mar/2020:19:03:58 +0000",
"timeEpoch": 1583348638390
},
"body": "{"name":"Alice"}",
"isBase64Encoded": false
}
// ── Lambda response for API Gateway (both v1 and v2) ───────────
// statusCode is required — missing it causes a 502 from API Gateway
export const handler = async (event) => {
const body = event.body ? JSON.parse(event.body) : {}
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({ message: 'ok', received: body }),
// isBase64Encoded: false // default — omit unless returning binary
}
}
// ── SQS batch event shape ──────────────────────────────────────
{
"Records": [
{
"messageId": "19dd0b57-1234-4f44-aee3-af62e6a1bfa0",
"receiptHandle": "MessageReceiptHandle",
"body": "{"orderId":"42","amount":99.99}",
"attributes": { "ApproximateReceiveCount": "1", "SentTimestamp": "1523232000000" },
"messageAttributes": {},
"md5OfBody": "7b270e59b47ff90a553787216d55d91d",
"eventSource": "aws:sqs",
"eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:MyQueue",
"awsRegion": "us-east-1"
}
]
}
// ── Context object key fields ──────────────────────────────────
// context.functionName — "my-function"
// context.functionVersion — "$LATEST" or "1"
// context.invokedFunctionArn — full ARN including alias
// context.memoryLimitInMB — "128"
// context.awsRequestId — unique invocation ID
// context.getRemainingTimeInMillis() — ms until timeout
// context.logGroupName — CloudWatch log group
// context.logStreamName — CloudWatch log streamThe body field in an API Gateway event is always a plain string — you must call JSON.parse(event.body) to get the parsed object. For multipart form data or binary payloads, isBase64Encoded is true and the body is a base64 string. S3 event records include an s3.object.key field that is URL-encoded — always call decodeURIComponent on the key before using it to retrieve the object, as S3 keys with special characters will be double-encoded.
DynamoDB Item Marshalling: AttributeValue JSON
DynamoDB's wire format wraps every value in an AttributeValue descriptor object with a single type key. The AWS SDK v3 provides marshall and unmarshall utilities in @aws-sdk/util-dynamodb to convert between plain JavaScript objects and AttributeValue format. The DynamoDBDocumentClient wraps the base client and applies marshalling transparently, letting you work with plain JSON in all CRUD operations.
import { DynamoDBClient } from '@aws-sdk/client-dynamodb'
import {
DynamoDBDocumentClient,
PutCommand,
GetCommand,
UpdateCommand,
QueryCommand,
} from '@aws-sdk/lib-dynamodb'
import { marshall, unmarshall } from '@aws-sdk/util-dynamodb'
const client = new DynamoDBClient({ region: 'us-east-1' })
const docClient = DynamoDBDocumentClient.from(client, {
marshallOptions: {
removeUndefinedValues: true, // omit undefined fields
convertEmptyValues: false, // do NOT convert "" to null
},
})
// ── AttributeValue type descriptors (raw wire format) ──────────
// Plain JS value → AttributeValue JSON
// "hello" → { "S": "hello" }
// 42 → { "N": "42" } (numbers stored as strings!)
// true → { "BOOL": true }
// null → { "NULL": true }
// Buffer/Uint8Array → { "B": "<base64>" }
// ["a", "b"] → { "L": [{"S":"a"},{"S":"b"}] }
// { key: "val" } → { "M": {"key":{"S":"val"}} }
// new Set(["a","b"]) → { "SS": ["a","b"] } (string set)
// new Set([1, 2]) → { "NS": ["1","2"] } (number set)
// ── marshall / unmarshall utilities ────────────────────────────
const plainItem = {
userId: 'user-123',
name: 'Alice',
age: 30,
active: true,
tags: ['admin', 'editor'],
address: { city: 'Seattle', zip: '98101' },
}
const marshalled = marshall(plainItem)
// {
// "userId": { "S": "user-123" },
// "name": { "S": "Alice" },
// "age": { "N": "30" },
// "active": { "BOOL": true },
// "tags": { "L": [{"S":"admin"},{"S":"editor"}] },
// "address":{ "M": {"city":{"S":"Seattle"},"zip":{"S":"98101"}} }
// }
const unmarshalled = unmarshall(marshalled)
// → back to the original plain JS object
// ── DocumentClient CRUD — works with plain JSON ────────────────
// Put item
await docClient.send(new PutCommand({
TableName: 'Users',
Item: plainItem,
ConditionExpression: 'attribute_not_exists(userId)', // prevent overwrite
}))
// Get item by primary key
const { Item } = await docClient.send(new GetCommand({
TableName: 'Users',
Key: { userId: 'user-123' },
}))
// Update item — ExpressionAttributeValues uses plain JSON (DocumentClient marshals)
await docClient.send(new UpdateCommand({
TableName: 'Users',
Key: { userId: 'user-123' },
UpdateExpression: 'SET #nm = :name, age = :age ADD loginCount :inc',
ExpressionAttributeNames: { '#nm': 'name' }, // 'name' is a reserved word
ExpressionAttributeValues: {
':name': 'Alice Updated',
':age': 31,
':inc': 1,
},
ReturnValues: 'ALL_NEW',
}))
// Query with FilterExpression
const { Items } = await docClient.send(new QueryCommand({
TableName: 'Users',
IndexName: 'StatusIndex',
KeyConditionExpression: '#status = :status',
FilterExpression: 'age > :minAge',
ExpressionAttributeNames: { '#status': 'status' },
ExpressionAttributeValues: {
':status': 'active',
':minAge': 25,
},
}))DynamoDB numbers are stored as strings internally to avoid floating-point precision issues — the N type always contains a string like "42" or "3.14". When using the raw DynamoDBClient (not DocumentClient), you must provide raw AttributeValue JSON in ExpressionAttributeValues; DocumentClient handles the conversion. Sets (SS, NS, BS) cannot contain duplicate values or empty strings — DynamoDB will reject the request with a ValidationException.
API Gateway Mapping Templates and JSON Transformation
API Gateway REST API mapping templates use the Velocity Template Language (VTL) to transform request and response JSON before passing it to or from an integration target. Mapping templates enable you to reshape payloads, extract fields, and construct new JSON objects without writing Lambda code. HTTP API lacks VTL support — use Lambda for transformation instead.
## ── Request mapping template (VTL) ──────────────────────────────
## Transform incoming API Gateway request body before sending to Lambda
## Content-Type: application/json → this template applies
#set($inputRoot = $input.path('$'))
{
"userId": "$input.params('userId')",
"name": "$inputRoot.name",
"email": "$inputRoot.email",
"timestamp": "$context.requestTimeEpoch",
"stage": "$context.stage"
}
## $input.json('$') → full request body as JSON string
## $input.json('$.field') → extract a field as JSON string
## $input.path('$') → parsed body as VTL object
## $input.params('paramName')→ path or query parameter value
## $util.parseJson(str) → parse a JSON string into a VTL object
## $util.toJson(obj) → serialize a VTL object to JSON string
## $util.escapeJavaScript(s) → escape a string for use in JSON
## ── Response mapping template ────────────────────────────────────
## Transform Lambda JSON response before sending to client
#set($outputRoot = $input.path('$'))
{
"id": "$outputRoot.userId",
"message": "$outputRoot.message",
"success": true
}
## ── Passthrough behavior ──────────────────────────────────────────
## When no mapping template matches the Content-Type:
## WHEN_NO_MATCH — pass request body through unchanged (default)
## WHEN_NO_TEMPLATES — only pass through if no templates defined at all
## NEVER — reject requests with unmatched content type (400)
## ── $input.json() JSONPath extraction examples ───────────────────
## $input.json('$.order.items[0].sku') → first item SKU as JSON string
## $input.json('$.tags') → tags array as JSON string
## $input.json('$.user.address.city') → nested field extraction
// ── HTTP API — no VTL, use Lambda for transformation ───────────
// HTTP API payload format v2 Lambda handler with transformation
export const handler = async (event) => {
const body = JSON.parse(event.body ?? '{}')
// Transform: rename fields, add computed values
const transformed = {
userId: event.pathParameters?.userId,
displayName: body.name?.trim(),
emailLower: body.email?.toLowerCase(),
receivedAt: new Date().toISOString(),
}
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(transformed),
}
}
// ── REST API vs HTTP API feature comparison ────────────────────
// Feature REST API HTTP API
// VTL mapping templates YES NO
// Request body validation YES NO
// Usage plans / API keys YES NO
// WAF integration YES NO
// Caching YES NO
// Median added latency ~7 ms ~1.5 ms
// Cost per million requests $3.50 $1.00
// Lambda proxy integration YES YES
// JWT authorizer NO YESVTL mapping templates execute on the API Gateway compute layer before invoking the backend, reducing Lambda invocations for simple transformations. Use $util.parseJson to convert a JSON string body into a VTL object when you need to access nested fields with the $input.path() accessor — $input.path() works on the raw body only if the body is already parsed. For conditional VTL logic, use #if/#else/#end directives to handle missing or optional fields in the incoming JSON.
Step Functions ASL JSON: States and Transitions
Amazon States Language (ASL) is the JSON-based language for defining Step Functions state machines. A state machine definition is a JSON object with a Comment string, a StartAt field naming the first state, and a States map of state definitions. Step Functions uses four JSON path fields — InputPath, Parameters, ResultPath, and OutputPath — to control how JSON data flows through each state transition.
// ── ASL state machine definition ──────────────────────────────
{
"Comment": "Order processing workflow",
"StartAt": "ValidateOrder",
"States": {
"ValidateOrder": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:123:function:ValidateOrder",
"InputPath": "$.order", // pass only the order field to Lambda
"ResultPath": "$.validation", // write Lambda output to $.validation
"OutputPath": "$", // pass full state (input + validation) forward
"Next": "CheckInventory",
"Retry": [
{
"ErrorEquals": ["Lambda.ServiceException", "Lambda.TooManyRequestsException"],
"IntervalSeconds": 2,
"MaxAttempts": 3,
"BackoffRate": 2.0
}
],
"Catch": [
{
"ErrorEquals": ["ValidationError"],
"Next": "HandleValidationError",
"ResultPath": "$.error"
}
]
},
"CheckInventory": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.validation.inStock",
"BooleanEquals": true,
"Next": "ProcessPayment"
},
{
"Variable": "$.order.quantity",
"NumericGreaterThan": 100,
"Next": "BackorderFlow"
}
],
"Default": "OutOfStockNotification"
},
"ProcessPayment": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "ProcessPayment",
"Payload": {
"orderId.$": "$.order.id", // .$ suffix = JSONPath reference
"amount.$": "$.order.totalAmount",
"currency": "USD" // literal value — no .$ suffix
}
},
"ResultSelector": {
"transactionId.$": "$.Payload.transactionId",
"status.$": "$.Payload.status"
},
"ResultPath": "$.payment",
"Next": "NotifyCustomer"
},
"ProcessBatch": {
"Type": "Map",
"ItemsPath": "$.order.lineItems", // iterate over JSON array
"MaxConcurrency": 5,
"Iterator": {
"StartAt": "ProcessLineItem",
"States": {
"ProcessLineItem": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:123:function:ProcessItem",
"End": true
}
}
},
"ResultPath": "$.processedItems",
"Next": "NotifyCustomer"
},
"WaitForApproval": {
"Type": "Wait",
"Seconds": 3600,
"Next": "CheckApproval"
},
"NotifyCustomer": {
"Type": "Task",
"Resource": "arn:aws:states:::sns:publish",
"Parameters": {
"TopicArn": "arn:aws:sns:us-east-1:123:OrderUpdates",
"Message.$": "States.Format('Order {} confirmed', $.order.id)"
},
"End": true
},
"HandleValidationError": {
"Type": "Fail",
"Error": "ValidationError",
"Cause": "Order validation failed"
}
}
}The .$ suffix on a key in Parameters indicates that the value is a JSONPath expression referencing the current input JSON — without the suffix, the value is a literal string. ResultPath: null discards the task output entirely and passes the original input unchanged to the next state. Step Functions intrinsic functions like States.Format, States.JsonMerge, States.StringToJson, and States.JsonToString enable JSON transformation within ASL without calling a Lambda function, reducing latency and cost.
EventBridge Rule JSON: Pattern Matching and Targets
EventBridge events are JSON objects with a standard envelope: source (who emitted the event), detail-type (event category string), detail (arbitrary JSON payload), plus id, time, region, account, and resources. Rule patterns are JSON objects that match against this envelope using content-based filtering — only events that match the pattern are routed to targets.
// ── EventBridge event envelope ─────────────────────────────────
{
"version": "0",
"id": "6a7e8feb-b491-4cf7-a9f1-bf3703467718",
"source": "com.myapp.orders",
"detail-type": "OrderPlaced",
"account": "123456789012",
"time": "2026-05-19T10:00:00Z",
"region": "us-east-1",
"resources": ["arn:aws:dynamodb:us-east-1:123:table/Orders"],
"detail": {
"orderId": "ORD-9876",
"status": "PENDING",
"amount": 149.99,
"customerId": "CUST-123",
"tags": ["premium", "express"],
"shipping": { "method": "overnight", "zip": "98101" }
}
}
// ── EventBridge rule patterns ──────────────────────────────────
// Exact string match on source and detail-type
{
"source": ["com.myapp.orders"],
"detail-type": ["OrderPlaced", "OrderUpdated"]
}
// Content-based filtering operators on detail fields
{
"source": ["com.myapp.orders"],
"detail": {
"status": ["PENDING", "PROCESSING"], // OR match — any value in array
"amount": [{ "numeric": [">", 100] }], // numeric comparison
"customerId": [{ "prefix": "CUST-" }], // prefix match
"shipping": {
"method": [{ "anything-but": ["standard"] }] // exclude values
}
}
}
// Exists / null checks
{
"source": ["com.myapp.orders"],
"detail": {
"promoCode": [{ "exists": true }], // field must be present
"cancelledAt": [{ "exists": false }] // field must NOT be present
}
}
// ── Input transformer — reshape JSON before sending to target ──
// Target: Lambda
// InputPath: selects a portion of the event JSON
// InputTemplate: constructs a new JSON string using extracted values
//
// InputPath: "$.detail"
// InputTemplate: '{"id": "<orderId>", "status": "<status>", "ts": "<aws.events.event.time>"}'
//
// Predefined variables: <aws.events.event>, <aws.events.event.id>,
// <aws.events.event.time>, <aws.events.event.region>,
// <aws.events.event.account>, <aws.events.event.source>,
// <aws.events.event.detail-type>
// ── Routing to multiple targets ────────────────────────────────
// One EventBridge rule can route to up to 5 targets simultaneously:
// - Lambda function (async invocation)
// - SQS queue (with optional message group ID for FIFO)
// - Step Functions state machine
// - SNS topic
// - API Gateway / API destination (HTTP endpoint)
// - EventBridge event bus (cross-account routing)
// ── Cross-account event bus policy ────────────────────────────
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::SOURCE_ACCOUNT_ID:root" },
"Action": "events:PutEvents",
"Resource": "arn:aws:events:us-east-1:TARGET_ACCOUNT_ID:event-bus/default",
"Condition": {
"StringEquals": { "events:source": "com.myapp.orders" }
}
}
]
}EventBridge pattern matching uses an implicit AND between top-level fields (all specified fields must match) and an implicit OR within each field's value array (any value in the array matches). The anything-but operator accepts a string, number, or array and matches events where the field does not equal any of the listed values — useful for filtering out noise events like "anything-but": ["CANCELLED", "FAILED"]. Use the EventBridge Sandbox in the AWS Console to test patterns against sample events before deploying rules.
CloudFormation and CDK JSON Output Patterns
CloudFormation templates can be written in either JSON or YAML; both produce identical stacks, but JSON is required when embedding template fragments in CDK applications or using Fn::Sub for string interpolation with embedded JSON. CDK synthesizes CloudFormation JSON from TypeScript/Python constructs via cdk synth. The aws cloudformation describe-stacks CLI command returns stack outputs as JSON and supports JMESPath filtering with --query.
// ── CloudFormation template JSON structure ─────────────────────
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "API + Lambda + DynamoDB stack",
"Parameters": {
"Environment": {
"Type": "String",
"AllowedValues": ["dev", "staging", "prod"],
"Default": "dev"
}
},
"Resources": {
"OrdersTable": {
"Type": "AWS::DynamoDB::Table",
"Properties": {
"TableName": { "Fn::Sub": "orders-${Environment}" },
"BillingMode": "PAY_PER_REQUEST",
"AttributeDefinitions": [
{ "AttributeName": "orderId", "AttributeType": "S" },
{ "AttributeName": "customerId", "AttributeType": "S" }
],
"KeySchema": [
{ "AttributeName": "orderId", "KeyType": "HASH" }
],
"GlobalSecondaryIndexes": [
{
"IndexName": "CustomerIndex",
"KeySchema": [{ "AttributeName": "customerId", "KeyType": "HASH" }],
"Projection": { "ProjectionType": "ALL" }
}
]
}
},
"OrdersFunction": {
"Type": "AWS::Lambda::Function",
"Properties": {
"FunctionName": { "Fn::Sub": "orders-handler-${Environment}" },
"Runtime": "nodejs20.x",
"Handler": "index.handler",
"Environment": {
"Variables": {
"TABLE_NAME": { "Ref": "OrdersTable" },
"ENVIRONMENT": { "Ref": "Environment" }
}
},
"Role": { "Fn::GetAtt": ["LambdaRole", "Arn"] }
}
}
},
"Outputs": {
"TableName": {
"Value": { "Ref": "OrdersTable" },
"Export": { "Name": { "Fn::Sub": "${AWS::StackName}-TableName" } }
},
"FunctionArn": {
"Value": { "Fn::GetAtt": ["OrdersFunction", "Arn"] },
"Export": { "Name": { "Fn::Sub": "${AWS::StackName}-FunctionArn" } }
}
}
}
// ── Fn::Sub for JSON string interpolation ──────────────────────
// Use Fn::Sub when you need to embed a literal JSON string with dynamic values
// (e.g., an EventBridge rule target input template)
{
"InputTemplate": {
"Fn::Sub": "{"tableName": "${OrdersTable}", "env": "${Environment}"}"
}
}
// ── Query stack outputs with JMESPath ──────────────────────────
// aws cloudformation describe-stacks --stack-name my-stack // --query 'Stacks[0].Outputs[?OutputKey==`TableName`].OutputValue' // --output text
// aws cloudformation describe-stacks --stack-name my-stack // --query 'Stacks[0].Outputs' --output json
// ── CDK JSON synthesis ─────────────────────────────────────────
// import * as cdk from 'aws-cdk-lib'
// import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'
//
// const app = new cdk.App()
// const stack = new cdk.Stack(app, 'OrdersStack')
//
// const table = new dynamodb.Table(stack, 'OrdersTable', {
// partitionKey: { name: 'orderId', type: dynamodb.AttributeType.STRING },
// billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
// })
//
// new cdk.CfnOutput(stack, 'TableName', { value: table.tableName })
//
// cdk synth --json → outputs CloudFormation JSON template
// cdk.out/OrdersStack.template.json → synthesized JSON artifactFn::Sub is the recommended CloudFormation intrinsic for string interpolation — it replaces ${LogicalId} placeholders with resource attributes and parameter values. When embedding JSON strings inside CloudFormation JSON (for example, in EventBridge target InputTemplate fields), escape inner double quotes with \\". CDK's cdk synth --json outputs a CloudFormation JSON template to stdout and writes cdk.out/StackName.template.json — inspect this file to debug construct synthesis issues or to manually inspect the generated IAM policies before deployment.
Key Terms
- AttributeValue
- The typed wrapper object DynamoDB uses to represent every item attribute on the wire. Each AttributeValue is a JSON object with exactly one key indicating the data type:
S(string),N(number stored as string),B(base64 binary),BOOL(boolean),NULL(null),L(list — ordered array of AttributeValues),M(map — string-keyed object of AttributeValues),SS(string set),NS(number set), orBS(binary set). The AWS SDK v3marshallandunmarshallutilities from@aws-sdk/util-dynamodbconvert between plain JavaScript objects and AttributeValue format.DynamoDBDocumentClientapplies marshalling transparently on all operations. - IAM policy statement
- A single JSON object within the
Statementarray of an IAM policy document that defines one permission rule. A statement contains:Effect("Allow"or"Deny"),Action(one or more AWS API action strings in the format"service:Operation"),Resource(one or more ARNs the actions apply to, or"*"for all resources), and optionallyPrincipal(the identity the statement applies to — used in resource-based policies),Condition(a nested JSON object of condition operators), andSid(a string identifier for the statement). Deny statements always override Allow statements from any policy, regardless of evaluation order. - ASL (Amazon States Language)
- The JSON-based specification language for AWS Step Functions state machines. An ASL document is a JSON object with
Comment,StartAt, andStatesfields. Each state inStateshas aTypefield selecting one of seven types: Task (calls an external resource), Choice (branches based on JSONPath conditions), Parallel (runs branches concurrently), Map (iterates over a JSON array), Pass (transforms JSON without external calls), Wait (delays execution), Succeed, or Fail. JSON data flows between states through four path fields:InputPath,Parameters,ResultPath, andOutputPath, which select and transform JSON using JSONPath expressions. - mapping template
- A Velocity Template Language (VTL) script attached to an API Gateway REST API integration that transforms the JSON request body before forwarding it to the backend, or transforms the backend JSON response before returning it to the caller. Mapping templates have access to
$input(the incoming request body and parameters),$context(request context including stage, account, and identity),$util(utility functions for JSON parsing and string escaping), and$stageVariables. They enable payload reshaping, field extraction via$input.json('$.fieldPath'), and conditional logic without writing Lambda code. HTTP API does not support mapping templates — all transformation must happen in Lambda. - content-based filtering
- An EventBridge feature that routes events to targets based on matching the event JSON body, not just the event source. A rule pattern is a JSON object that specifies which field values must be present for an event to match. Supported operators include exact match (array of literal values), prefix (
{"{"}"prefix": "value"{"}"}), suffix, numeric comparison ({"{"}"numeric": [{'">'}, 100]{"}"}), anything-but, exists (field presence check), and null check. Multiple fields in a pattern are implicitly ANDed; multiple values in a field's array are implicitly ORed. Content-based filtering allows a single event bus to serve many consumers with precise routing, replacing topic-per-event-type fan-out patterns. - marshalling
- The process of converting a native programming language data structure (such as a JavaScript object or Python dictionary) into the wire format expected by an external system. In the AWS DynamoDB context, marshalling converts plain JSON objects into DynamoDB AttributeValue format — for example, converting the string
"hello"to{"S": "hello"}and the number42to{"N": "42"}. Unmarshalling is the reverse: converting AttributeValue format back to plain objects. The AWS SDK v3 provides themarshallandunmarshallfunctions in@aws-sdk/util-dynamodband applies them automatically throughDynamoDBDocumentClient.
FAQ
What JSON format does API Gateway expect from a Lambda function?
API Gateway expects a Lambda function to return a JSON object with three mandatory top-level fields: statusCode (an integer HTTP status code), headers (an object of string key-value pairs for HTTP response headers), and body (always a string — if you need to return a JSON body, JSON.stringify it first). A missing statusCode causes API Gateway to return a 502 Bad Gateway to the caller. For binary responses, add "isBase64Encoded": true and provide the body as a base64-encoded string. Payload format v1 (REST API) and v2 (HTTP API) differ slightly: v2 uses a cookies top-level array and combines multi-value headers into comma-separated strings, while v1 uses multiValueHeaders and multiValueQueryStringParameters as separate objects.
How does DynamoDB store JSON data internally?
DynamoDB stores every attribute as an AttributeValue descriptor: a JSON object with one key indicating the type. Strings become {"S": "value"}, numbers become {"N": "42"} (the number is stored as a string to preserve precision), booleans become {"BOOL": true}, null becomes {"NULL": true}, binary data becomes {"B": "<base64>"}, lists become {"L": [...]}, maps become {"M": {...}}, and sets become SS, NS, or BS. The DynamoDB API sends and receives this wire format. Use the AWS SDK v3 marshall/unmarshall utilities or DynamoDBDocumentClient to convert between AttributeValue format and plain JavaScript objects automatically.
What is the IAM policy JSON structure?
An IAM policy document is a JSON object with two top-level fields: Version (always "2012-10-17") and Statement (an array of statement objects). Each statement must include Effect ("Allow" or "Deny"), Action (a string or array of AWS API action strings like "s3:GetObject"), and Resource (a string or array of resource ARNs). Optional fields include Principal (for resource-based policies), Condition (a nested object using operators like StringEquals, ArnLike, or IpAddress), and Sid (a statement identifier). Managed policies have a 6,144-character JSON limit; inline policies attached to IAM users have a 2,048-character limit.
How do I pass JSON between Step Functions states?
Step Functions controls JSON data flow with four path fields per state. InputPath (default "$") selects which portion of the incoming JSON to pass to the state's task — use a JSONPath expression like "$.order" to pass only the order field. Parameters constructs a new JSON object for the task; keys ending in .$ reference JSONPath values from the input (e.g., "orderId.$": "$.order.id"). ResultPath controls where the task output is written into the state data — "$.result" adds it as a new field, null discards it, "$" replaces the entire state. OutputPath filters the final state JSON before passing it to the next state. Use ASL intrinsic functions like States.JsonMerge and States.StringToJson for in-state JSON transformation.
How do I filter EventBridge events with JSON patterns?
EventBridge rule patterns are JSON objects where each key matches against the corresponding field in the event envelope. A pattern matches when all specified fields match (implicit AND). Each field's value is an array where any element can match (implicit OR for literal values). Supported matching operators: exact match (["value"]), prefix ([{'{"prefix": "CUST-"}'}]), suffix ([{'{"suffix": ".json"}'}]), anything-but ([{'{"anything-but": ["CANCELLED"]}'}]), numeric comparison ([{"numeric": [">", 100, "<=", 500]}]), exists ([{'{"exists": true}'}]), and null check ([{'{"null": true}'}]). Nest pattern objects to match fields within the detail object. Test patterns in the EventBridge Sandbox in the AWS Console before deploying rules to production.
What is the difference between API Gateway REST API and HTTP API for JSON?
REST API and HTTP API are two distinct API Gateway products with different JSON handling capabilities. REST API supports VTL mapping templates for request/response JSON transformation, request body validation against a JSON Schema, usage plans with API keys, a dedicated caching layer, WAF integration, and edge-optimized endpoints — at approximately 7 ms added median latency and $3.50 per million requests. HTTP API skips all of those features in exchange for approximately 1.5 ms added median latency (4-5x faster) and $1.00 per million requests (71% cheaper). HTTP API also natively supports JWT authorizers and the payload format v2 Lambda event shape. For pure JSON proxy patterns where Lambda handles all validation and transformation logic, use HTTP API. Use REST API only when you need VTL templates, request validation, usage plans, or WAF.
How do I query DynamoDB with JSON filter expressions?
DynamoDB Query and Scan operations accept a FilterExpression string and an ExpressionAttributeValues JSON object mapping placeholder names (prefixed with :) to their values. The FilterExpression uses comparison operators (=, <>, <, >, <=, >=), function checks (attribute_exists, attribute_not_exists, begins_with, contains, size), and logical connectors (AND, OR, NOT). For attribute names that are DynamoDB reserved words (like status, name, or year), use ExpressionAttributeNames to map a #-prefixed placeholder. When using DynamoDBDocumentClient, ExpressionAttributeValues uses plain JSON values; the raw client requires AttributeValue format. Note that FilterExpression is applied after DynamoDB reads up to 1 MB — it does not reduce consumed read capacity.
How do I handle large JSON payloads in Lambda?
Lambda has a 6 MB synchronous invocation payload limit (request + response combined) and a 256 KB asynchronous invocation limit for event sources like SQS and SNS. API Gateway limits request and response bodies to 10 MB. For large JSON payloads, use S3 as an intermediary: write the large JSON object to S3, pass only the bucket name and key in the Lambda event, and have the function read directly from S3. For large responses, return a pre-signed S3 URL instead of the JSON body. Lambda response streaming (via awslambda.streamifyResponse and the InvokeWithResponseStream API) supports up to 20 MB and begins sending bytes before the full response is computed — useful for streaming large JSON arrays. For SQS batch processing of large messages, use S3 event notifications and process the S3 object key from the SQS message body.
Further reading and primary sources
- IAM JSON Policy Reference — Official AWS reference for all IAM policy JSON elements, condition operators, and policy variables
- AWS Lambda Event Source Mappings — JSON event shapes for all Lambda trigger sources including API Gateway, SQS, SNS, and S3
- DynamoDB: Working with Items — Expressions — FilterExpression, UpdateExpression, and ExpressionAttributeValues JSON syntax reference
- Amazon States Language Specification — Complete ASL JSON specification for Step Functions state machine definitions
- EventBridge Content-Based Filtering — EventBridge pattern matching operators for JSON content-based filtering