为什么这个 JSON 属性 被记录为未定义?

Why is this JSON property being logged as undefined?

我有一个 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 }

我已经对三个日志行进行了注释,因为这样可以更容易讨论。

log1: 这显示了事件的纯文本。 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

这是我在学习 typescript/node lambdas 所以要温柔。

在此处保持关于 JSON 封装的正确概念导向可能会有点棘手,因为多个服务在级联中交互。

当 AWS 服务与使用 Node.js Lambda 运行时部署的函数交互以提供事件时,它们实际上将整个调用负载作为在线路上的 JSON 对象提供。 JSON 这一层实际上您并不感兴趣,因为 Lambda 透明地将其解析为适当的 JavaScript 对象并将其作为 event.

交给您

当SQS/Lambda集成聚合事件时,在event对象中有一个event structure外层Records数组,数组的每个成员包含从 SQS ReceiveMessages API 操作接收到的单个 SQS 消息的属性。这一层也有 JSON 序列化,但同样,它是透明处理的,完成的会被撤消,所以没有意义。

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

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

如果您正在捕获自己发布的 SQS 消息,则此 body 将包含通过 SendMessage 调用发送到 SQS 的消息正文字节。它将是透明的。无论您输入什么,都会输出什么,无论是纯文本还是 Base-64 或 JSON 或 XML 等

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

当您将 SNS 连接到 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" 表示消息正文 - 这就是您在 body 属性 中找到的内容,例如event.Records[0].body.

body中的"JSON document"实际上是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 消息(SNS 从 SES 接收的消息)的负载也是一个 JSON 对象。既然如此:

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

请注意,我们将 body 解析为一个对象,从结果对象中获取 Message 属性——这是一个包含 JSON 对象的字符串——并且将其解析为另一个对象。从顶部开始,我们在上面一行中所做的解码最内层消息的操作等同于此 - 此处显示是为了说明原理:

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 的字符串。它的 return 类型是字符串。这是 "encode" 或 "serialize" 或 "to JSON" 操作。

JSON.parse() 需要一个 JSON 对象——即包含 JSON 的字符串变量,并将其转换回 JavaScript 对象、数组、字符串、数字、布尔值或空值。它的 return 类型取决于什么被序列化到最外层的 JSON 字符串中。这是 "decode" 或 "deserialize" 或 "from JSON" 操作。如果 JSON 对象中的任何字符串包含 JSON,则解码不是递归的。它们被解码为字符串,而不是其中的对象,因此如果您想将其中的对象作为 JavaScript 对象访问,则需要针对这些结果字符串添加 JSON.parse() 层。