Understanding the JSON encapsulation in a multi-service interaction scenario can be tricky, as different services are communicating in a cascade.
When AWS services interact with Node.js Lambda functions to provide events, they send the entire invocation payload as a JSON object over the wire. However, Lambda automatically parses this layer of JSON into a JavaScript object and provides it to you as event
.
In the case of SQS/Lambda integration handling events, an outer Records
array exists within the event
object, where each member represents properties of a single SQS message obtained from the SQS ReceiveMessages
API action. Despite JSON serialization at this stage, Lambda transparently handles this transformation so that it's not a concern.
(Lambda's SQS integration operates using hidden managed servers that fetch messages from the SQS queue and submit them as function invocations.)
One key property found in each object inside the Records
array is body
, which holds the payload from the SQS message.
If you were retrieving an SQS message you published yourself, the body
would contain the exact bytes sent to SQS through the SendMessage
call. The content remains intact whether it's plain text, Base-64 encoded, JSON, XML, etc.
However, when your SQS queue is subscribed to an SNS topic:
The Amazon SQS message contains the subject and message published to the topic, along with message metadata in a JSON format.
https://docs.aws.amazon.com/sns/latest/dg/sns-sqs-as-subscriber.html
The "Amazon SQS message" mentioned above refers to the message body, which is captured in the body
property like event.Records[0].body
.
This JSON document in the body
is generated by SNS.
When SNS dispatches a message to SQS, it adds another layer of JSON around its output to preserve all message attributes rather than just the body (referred to as Message
by SNS).
Therefore, what you receive here is the JSON-encoded body
sent to SQS by SNS, which you simply need to parse using JSON.parse()
.
let incomingMessage = JSON.parse(event.Records[0].body);
let type = incomingMessage.Type;
console.log(type); // 'Notification'
You'll also discover that the payload of the actual SNS message received from SES is itself a JSON object. Consequently:
let message = JSON.parse(incomingMessage.Message);
While this may seem complex initially, the nesting of JSON ensures clean round trips without confusing object boundaries. Both SNS and SQS support only text payloads, prompting SES to create a JSON representation for communication with SNS, which in turn formats the data as JSON before sending it to SQS. Undoing these two layers of JSON serialization is necessary to process notifications efficiently across the SES > SNS > SQS > Lambda chain.
Just a quick reminder:
JSON.stringify()
converts JavaScript objects, arrays, strings, numbers, booleans, or null values into a JSON string. It encodes or serializes data into JSON format.
JSON.parse()
decodes JSON back into a JavaScript object, array, string, number, boolean, or null, depending on the serialized data structure. It deserializes JSON data into corresponding JavaScript entities. If any strings within the JSON object contain nested JSON structures, additional JSON.parse()
calls are required for access.