Why is this JSON property being logged as undefined?

非 Y 不嫁゛ 提交于 2020-05-30 08:01:18

问题


I have a nodejs lambda that has an SQS queue as an event, which is subscribed to an SNS topic.

The lambda looks like this:

'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 annotated the three log lines because it'll make it a bit easier to talk about.

log1: This shows the bare text of the event. log2: This shows a nice JSON formatted (thank you cloudwatch) 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: This just logs undefined

I don't understand why it's showing as undefined and not Notification.

This is me learning typescript/node lambdas so be gentle.


回答1:


Remaining correctly conceptually oriented with regard to the JSON encapsulation here can get a little tricky, because multiple services are interacting in a cascade.

When AWS services interact with functions deployed using the Node.js Lambda runtimes to supply events, they actually supply the entire invocation payload as a JSON object on the wire. This layer of JSON isn't actually of interest to you, because Lambda transparently parses this into a proper JavaScript object and hands it to you as event.

When the SQS/Lambda integration is aggregating events, there is an event structure with an outer Records array in the event object, and each member of the array contains the properties of a single SQS message, as received from the SQS ReceiveMessages API action. There is JSON serialization at this layer as well, but once again, it is transparently handled and what's done gets undone, so it is of no interest.

(Lambda's SQS integration actually provides you with a group of hidden, managed servers that poll the SQS queue to collect these messages and submit them to Lambda as function invocations.)

Among the properties in each object inside the Records array is body, which contains a string with the payload from the SQS message.

If you were capturing an SQS message you had published, yourself, this body would contain exactly the message body bytes sent to SQS with the SendMessage call. It would be transparent. Whatever you put in is what you'd get out, whether it was plain text or Base-64 or JSON or XML, etc.

However... you have an SQS queue that is subscribed to an SNS topic.

When you connect SNS to SQS:

The Amazon SQS message contains the subject and message that were published to the topic along with metadata about the message in a JSON document.

https://docs.aws.amazon.com/sns/latest/dg/sns-sqs-as-subscriber.html

"The Amazon SQS message" referred to above means the message body -- and this is what you find in the body property, e.g. event.Records[0].body.

The "JSON document" in the body is actually created by SNS.

When SNS delivers a message to SQS, it adds a layer of JSON encapsulation to its own output, so that the other properties of the message are preserved, not just the body payload (which SNS calls Message).

Thus what you are receiving here is the body supplied to SQS by SNS, which SNS has encoded in JSON. All you need to do is parse that into a JavaScript object using JSON.parse().

let incomingMessage = JSON.parse(event.Records[0].body); 
let type = incomingMessage.Type;
console.log(type); // 'Notification'

You are also likely to find that the payload of the actual SNS message (the message SNS received from SES) is a JSON object as well. That being the case:

let message = JSON.parse(incomingMessage.Message);

Note here that we're parsing body into an object, taking the Message attribute from the resulting object -- which is a string containing a JSON object -- and parsing that into another object. From the top, what we're doing, in the line above, to decode that innermost message is equivalent to this -- shown here for illustration of the principle:

let message = JSON.parse(JSON.parse(event.Records[0].body).Message);

This may initially strike you as quite complex and convoluted, but there are good reasons why this is necessary. JSON supports perfect nesting of other JSON and clean round trips, without confusing object boundaries. SNS and SQS both support delivering only text -- character data -- as their payload... so SES creates a JSON representation of what it wants to tell you and sends it to SNS... then SNS creates a JSON representation of what it needs to tell you and sends it to SQS... so there are two layers of JSON serialization that you will ultimately need to undo, in order to process SES > SNS > SQS > Lambda event notifications.


As a reminder:

JSON.stringify() expects a JavaScript object, array, string, number, boolean, or null and serializes it into a string containing JSON. Its return type is string. This is the "encode" or "serialize" or "to JSON" operation.

JSON.parse() expects a JSON object -- that is, a string variable containing JSON, and converts it back to a JavaScript object, array, string, number, boolean, or null. Its return type depends on what's been serialized into the JSON string at the outermost layer. This is the "decode" or "deserialize" or "from JSON" operation. If any strings within the JSON object contain JSON, the decoding is not recursive. They are decoded as strings, and not the objects within, so an addition layer of JSON.parse() against those resulting strings is needed if you want to access the object within as a JavaScript object.



来源:https://stackoverflow.com/questions/56912436/why-is-this-json-property-being-logged-as-undefined

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!