入门阅读约 2 分钟

DynamoDB JSON 与 Marshalling

你第一次从 DynamoDB API 读到原始数据时,它看起来不像你放进去的那个 JSON。一个像 {"status": "open", "priority": 3} 的普通对象,返回时成了 {"status": {"S": "open"}, "priority": {"N": "3"}}。每个值都被包进一个标明其类型的单键对象里。 那层包裹就是 DynamoDB JSON,而在它与普通形式之间来回转换,叫作 marshalling

这不是噪声 —— 这是 DynamoDB 在传输线上保持类型无歧义的方式。但它会绊倒任何期待普通 JSON 的人, 而手写它又容易出错。

什么是 DynamoDB JSON?

DynamoDB JSON 是 DynamoDB 使用的带类型标签的传输格式,其中每个值都被包裹在一个标明其类型的单键对象里 —— 字符串用 {"S": "open"},数字用 {"N": "3"}。在普通 JSON 与它之间来回转换的过程叫作 marshalling。这套格式让类型始终无歧义,因为普通 JSON 无法表达集合、二进制,也无法区分字符串 "3" 和数字 3

  • DynamoDB JSON 给每个值都标上它的类型 —— 字符串用 {"S": "..."},数字用 {"N": "..."}, 依此类推。
  • Marshalling = 普通 JSON → DynamoDB JSON。Unmarshalling = 反过来。
  • 数字在传输线上是字符串 —— {"N": "3"},而非 {"N": 3} —— 以保留精度。
  • 那些类型标签就是你早已在用来建模的数据类型系统:S、N、B、BOOL、NULL、L、M、SS、NS、BS。
  • 别手写它。 SDK 的文档客户端(或一个转换器)会替你做 marshalling;只有在调试或构建表达式时 才手动去做。

问题所在:普通 JSON 不够用

JSON 恰好只有三种标量 —— 字符串、数字、布尔值 —— 外加 null、数组和对象。DynamoDB 有更多:二进制, 以及三种 JSON 根本无法表达的集合类型(字符串集合、数字集合、二进制集合)。JSON 也分不清一个 字符串 "3" 和一个数字 3,或者一个列表和一个集合。

所以 DynamoDB 不能原样存下你的 JSON —— 它需要每个值的确切类型被显式说明。类型描述符就是它做到 这一点的方式,无损地,在每一次请求和响应上。

这套编码如何工作

每个属性值都变成一个单键对象,其键是一个类型描述符

描述符类型示例
S字符串{"S": "open"}
N数字(作为字符串){"N": "3"}
B二进制{"B": "dGV4dA=="}
BOOL布尔值{"BOOL": true}
NULL空值{"NULL": true}
L列表{"L": [{"S": "a"}, {"N": "1"}]}
M映射{"M": {"k": {"S": "v"}}}
SS / NS / BS字符串/数字/二进制集合{"SS": ["a", "b"]}

列表和映射把同样的描述符一路嵌套到底,所以一个结构很深的项就被包裹得很深。数字在传输线上是 字符串,这是有意为之 —— 它让 DynamoDB 能保留任意精度,而一个 JSON 数字(一个 IEEE-754 双精度) 会悄悄把它舍入掉。这些就是你用来建模的同一套数据类型;DynamoDB JSON 不过是 它们在传输线上的显式形态,定义在 AWS 底层 API 参考中。

实战示例:一条审计日志条目

你会在应用里写的普通 JSON:

{
  "actor": "u-204",
  "action": "ticket.close",
  "ticketId": 8842,
  "tags": ["billing", "urgent"],
  "redacted": false
}

为 API 而 marshal 成的 DynamoDB JSON:

{
  "actor": {"S": "u-204"},
  "action": {"S": "ticket.close"},
  "ticketId": {"N": "8842"},
  "tags": {"SS": ["billing", "urgent"]},
  "redacted": {"BOOL": false}
}

注意 marshaller 做出的选择:ticketId 变成了 N,值是一个字符串tags 变成了一个字符串 集合SS),而不是一个列表 —— 这是一个刻意的决定,因为集合会去重且无序。tags 该是 SS 还是 L,是一个转换器无法替你做的建模抉择,而这恰恰是为什么理解这套编码很重要。

在 DynoTable 中转换

你很少需要手写或手读它。把普通 JSON 粘进 DynamoDB JSON 转换器来 marshal 它(以及反向),而当你在组装 一个请求时,DynamoDB 表达式构建器会在表达式旁边一并输出 正确 marshal 好的属性值映射。在应用本身里,DynoTable 把项展示为普通、可读的值,并在写入时替你 marshal 它们。

DynoTable 把一个项展示为普通值,同时提供原始的 DynamoDB JSON。
DynoTable 把一个项展示为普通值,同时提供原始的 DynamoDB JSON。

坑与后续步骤

  • 在 DynamoDB JSON 里数字是字符串 —— {"N": "3"}。引号很要紧;别输出一个裸数字。
  • 集合还是列表是一个建模决定,被这套编码摆到了明面上 —— 要刻意选择(见数据类型)。
  • 在应用代码里优先用 SDK 文档客户端,而非手动 marshalling;把手写 DynamoDB JSON 留给调试和 表达式。
  • 空字符串在项里是被允许的,但历来会绊倒一些工具 —— 验证边界情形。

想把项当作普通值来浏览,而不是用肉眼解码类型标签? 下载 DynoTable,直接处理你的数据。

更新于