What is the reason for this JSON attribute showing up as undefined in the logs?

Recently, I've set up a nodejs lambda function that is triggered by an SQS queue connected to an SNS topic.

Here's a snippet of the lambda code:

'use strict';

import { Handler } from 'aws-lambda';

const myLambda: Handler = async (event: any = {}) => {
  let incomingMessage = JSON.stringify(event.Records[0].body);
  console.log('Received event:', incomingMessage); # log1
  console.log('parsed event',JSON.parse(incomingMessage)); # log2
  var type = JSON.parse(JSON.stringify(incomingMessage)).Type;
  console.log('Message received from SNS:', type); # log3
  return { };
};

export { myLambda }

I've added annotations to three specific log lines for clarity and discussion purposes.

log1: Displays the raw content of the event. log2: Shows a neatly formatted JSON representation of the message.

{
    "Type": "Notification",
    "MessageId": "9245d801-2fe5-58ed-b667-8d9b73b2ff85",
    "TopicArn": "arn:aws:sns:eu-west-1:0123456:TopicName",
    "Subject": "Amazon SES Email Receipt Notification",
    "Message": "{json goes here}",
    "Timestamp": "2019-07-06T08:21:43.474Z",
    "SignatureVersion": "1",
    "Signature": "Signature goes here",
    "SigningCertURL": "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-1234567.pem",
    "UnsubscribeURL": "https://url.goes.here"
}

log3: Unfortunately, this only logs undefined.

I'm puzzled as to why it's displaying undefined instead of Notification.

This journey into typescript/node lambdas is a learning experience, so any guidance is appreciated.

Answer №1

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.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Developing a custom React hook that utilizes the useContext functions

When attempting to utilize a function within a custom hook, I encounter the following error message: Error: tglCartao is not defined The custom hook code in UseCartao.tsx is as follows: export interface ICartaoContext { idToggleKey : string; ...

Show the key and value of a JSON object in a React component

When attempting to parse a JSON data file, I encountered an error message stating: "Element implicitly has an 'any' type because expression of type 'string' can't be used to the index type." The JSON data is sourced locally from a ...

Troubleshooting problems with styled and typed components in ReactJS

I created a styled component with the following structure: export const TestClassButton = styled(Button)({ ... }) Here is an example of how I implement it: <Tooltip arrow title={"status"}> <TestClassButton id={"button-statu ...

React / NextJS: Repeating Audiowave Component

I am currently developing a chat application in NextJS that includes text-to-speech functionality. To visualize the audio playback waveform, I have integrated a third-party library called wavesurfer.js Though the audio implementation is functioning proper ...

Guide on specifying the return type for Rx.Observable.fromPromise() in TypeScript

My current problem involves a function that returns an Rx.Observable created from a promise. Here is the code snippet: var data$ = fetchData(); fetchData() { return Rx.Observable.fromPromise(fetch("someUrl")); } When I hover over the variable data$ i ...

Verify if the date surpasses the current date and time of 17:30

Given a date and time parameters, I am interested in determining whether that date/time is greater than the current date at 17:30. I am hoping to achieve this using moment js. Do you think it's possible? This is what I have been attempting: let ref ...

How to resolve useState problem in useEffect when state is not null

Encountering issues with maintaining state in TypeScript React. A Child component passes a 'terminal' object to the Parent via a function called returnTerminal(). This 'terminal' object is then stored as a useState variable named _obje ...

Circular referencing in Angular models causes interdependence and can lead to dependency

I'm facing a dependency issue with the models relation in my Angular project. It seems to be an architecture problem, but I'm not sure. I have a User model that contains Books, and a Book model that contains Users. When I run this code, I encoun ...

Dealing with throwing Exceptions in jest: A guide for developers

I have developed a method that throws an exception when the provided password does not match a regex pattern. I attempted to handle this in Jest. it('Should prevent insertion of a new user if the password doesn't match the regex', async () ...

Transform the subscription into a string

When I use the http method to retrieve a single user, the output logged in the console looks like this: this.usersService.getOneUser().subscribe(data => { console.log(data) }); email: "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data- ...

Creating and managing global context with useReducer in TypeScript and React

One issue that I am facing is with the route containing a login and logout button, where the browser error states 'Property 'dispatch' does not exist on type '{}'. (home.tsx) import React, { useContext, useEffect, useRef, use ...

View unprocessed HTML content - Angular 6

I want to showcase the raw HTML code (example.component.html) below 'example works!'. The page should display as follows: example works! <p> example works! </p> While there are resources available on how to do this with Ang ...

Ways to extract the final digit from a format such as an IP address in JavaScript

Is there a way to retrieve the last digits 192.168.1.180 For instance: From the IP address 192.168.1.180, I would like to extract 180. Thank you in advance ...

Steps to generate an unlimited tree structure using a specified set of data organized by parent ID

I have a collection structured like this: interface Elm { id: number; name: string; parent?: number; } Now, I would like to transform it into the following format: interface NodeTree { id: number; name: string; children: NodeTree[]; parent?: ...

Balancing website speed with capturing product impression

I've been tasked with capturing impressions of all the products visible in the viewport on a website that features thousands of products. To achieve this, I implemented a directory and utilized the IntersectionObserver, which was referenced within the ...

Breaking down an object using rest syntax and type annotations

The interpreter mentions that the humanProps is expected to be of type {humanProps: IHumanProps}. How can I properly set the type for the spread operation so that humanPros has the correct type IHumanProps? Here's an example: interface IName { ...

How to apply dynamic styling to a MatHeaderCell using NgStyle?

My goal is to dynamically style a MatHeaderCell instance using the following code: [ngStyle]="styleHeaderCell(c)" Check out my demo here. After examining, I noticed that: styleHeaderCell(c) It receives the column and returns an object, however ...

The function cannot be called because the type does not have the appropriate signature for invoking. The specific type lacks compatible call signatures, as indicated

Encountering an issue while attempting to utilize a getter and setter in my service, resulting in the following error message: Cannot invoke an expression whose type lacks a call signature. Type 'Boolean' has no compatible call signatures 2349 t ...

The type 'HTMLInputElement | HTMLTextAreaElement' cannot be assigned to type 'HTMLInputElement' in this context

I'm encountering an issue with updating the state using hooks. The goal is to update the todo with event.target.value, but I keep getting an error. Error: I'm facing the following issue when trying to update the state using hooks. The intention ...

What is the best way to connect a JSON object with a variable in TypeScript Angular 2?

I have a single json file that I need to connect with a variable in order to display it on the user interface. testjson= {"date":1468497879354,"faulty":"F-3","mileage":"150,900 mls","search":[]} Also, in the HTML code: <div> <span>{{ test ...