为什么将此JSON属性记录为未定义?

时间:2019-07-06 08:26:01

标签: typescript aws-lambda amazon-sns

我有一个nodejs lambda,它具有一个SQS队列作为事件,并已订阅了SNS主题。

lambda看起来像这样:

'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 }

我已经注释了三行日志,因为这样会使讨论变得容易一些。

日志1::显示事件的纯文本。 log2::此消息显示了一个很好的JSON格式(感谢cloudwatch):

{
    "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:这仅记录undefined

我不明白为什么它显示为undefined而不是Notification

这是我在学习打字稿/节点lambdas,所以要保持柔和。

1 个答案:

答案 0 :(得分:2)

在这里,关于JSON封装在概念上保持正确定位可能会有些棘手,因为多个服务正在级联中交互。

当AWS服务与使用Node.js Lambda运行时部署的功能进行交互以提供事件时,它们实际上将整个调用有效负载作为JSON对象提供给网络。 JSON实际上并不吸引您,因为Lambda透明地将其解析为适当的JavaScript对象,并将其作为event交给您。

当SQS / Lambda集成正在聚集事件时,Records对象中有一个event structure和一个外部event数组,并且数组的每个成员都包含一个从SQS ReceiveMessages API操作收到的单个SQS消息。在这一层上也有JSON序列化,但是再一次,它被透明地处理,并且所做的操作被撤消,因此没有兴趣。

(Lambda的SQS集成实际上为您提供了一组隐藏的托管服务器,这些服务器轮询SQS队列以收集这些消息并将它们作为函数调用提交给Lambda。)

Records数组中每个对象的属性中有body,其中包含带有SQS消息有效负载的字符串。

如果您自己捕获自己发布的SQS消息,则此body将包含通过SendMessage调用发送给SQS的消息正文字节。这将是透明的。不管输入的是纯文本,Base-64还是JSON或XML,等等。

但是...您有一个已订阅SNS主题的SQS队列。

将SNS连接到SQS时:

  

Amazon SQS消息包含发布到主题的主题和消息以及JSON文档中有关消息的元数据。

     

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

上面提到的“ Amazon SQS消息”表示消息正文,这就是您在body属性中找到的内容,例如event.Records[0].body

body中的“ JSON文档”实际上是由SNS创建的。

当SNS将消息传递到SQS时,它将在其自己的输出中添加一层JSON封装,以便保留消息的其他属性,而不仅保留主体有效负载(SNS称为Message)。

因此,您在这里收到的是SNS提供给SQS的body,SNS已使用JSON对其进行了编码。您需要做的就是使用JSON.parse()将其解析为JavaScript对象。

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

您还可能会发现实际SNS消息(从SES接收到的消息SNS)的有效负载也是JSON对象。就是这种情况:

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

请注意,我们正在将body解析为一个对象,并从结果对象(是一个包含JSON对象的字符串)中获取Message属性,然后将其解析为另一个对象。从顶部开始,我们在上一行中所做的用于解码最里面的消息的操作与此等效—在此处显示该原理的说明:

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

这最初可能会让您感到非常复杂和令人费解,但是有充分的理由说明为什么有必要这样做。 JSON支持其他JSON的完美嵌套和干净的往返,而不会混淆对象边界。 SNS和SQS都只支持传递文本-字符数据-作为有效载荷...因此SES creates a JSON representation of what it wants to tell you并将其发送给SNS ...然后SNS为需要告诉您的内容创建一个JSON表示并将其发送到SQS ...,因此最终需要撤消JSON序列化的两层,以便处理SES> SNS> SQS> Lambda事件通知。


提醒一下:

JSON.stringify()需要JavaScript对象,数组,字符串,数字,布尔值或null,并将其序列化为包含JSON的字符串。它的返回类型是字符串。这是“编码”或“序列化”或“至JSON”操作。

JSON.parse()需要一个JSON对象-即包含JSON的字符串变量,并将其转换回JavaScript对象,数组,字符串,数字,布尔值或null。它的返回类型取决于最外层序列化到JSON字符串的内容。这是“解码”或“反序列化”或“来自JSON”操作。如果JSON对象中的任何字符串包含JSON,则解码不是递归的。它们被解码为字符串,而不是字符串中的对象,因此,如果您想将其中的对象作为JavaScript对象访问,则需要对这些结果字符串加上JSON.parse()的附加层。