DynamoDB JSON & marshalling
La première fois que tu lis des données brutes depuis l'API DynamoDB, ça ne ressemble pas au
JSON que tu as mis dedans. Un objet ordinaire comme {"status": "open", "priority": 3}
revient sous la forme {"status": {"S": "open"}, "priority": {"N": "3"}}. Chaque valeur est
enveloppée dans un objet à une clé nommant son type. Cet enveloppement est le DynamoDB
JSON, et la conversion vers et depuis lui s'appelle le marshalling.
Ce n'est pas du bruit — c'est ainsi que DynamoDB garde les types non ambigus sur le fil. Mais ça déroute quiconque s'attend à du JSON ordinaire, et l'écrire à la main est source d'erreurs.
Qu'est-ce que le DynamoDB JSON ?
Le DynamoDB JSON est le format filaire typé que DynamoDB utilise, où chaque valeur est enveloppée dans un objet à une clé nommant son type — {"S": "open"} pour une chaîne, {"N": "3"} pour un nombre. Convertir du JSON ordinaire vers lui (et inversement) s'appelle le marshalling. Il garde les types non ambigus, puisque le JSON ordinaire ne peut pas exprimer les sets ni le binaire, et parce que les nombres DynamoDB circulent sur le fil comme des chaînes, un 3 sans balise serait ambigu.
- Le DynamoDB JSON balise chaque valeur avec son type —
{"S": "..."}pour une chaîne,{"N": "..."}pour un nombre, et ainsi de suite. - Marshalling = JSON ordinaire → DynamoDB JSON. Unmarshalling = l'inverse.
- Les nombres sont des chaînes sur le fil —
{"N": "3"}, pas{"N": 3}— pour préserver la précision. - Les balises de type sont le système de types de données que tu modélises déjà : S, N, B, BOOL, NULL, L, M, SS, NS, BS.
- Ne l'écris pas à la main. Le document client du SDK (ou un convertisseur) marshalle pour toi ; ne le fais manuellement que pour déboguer ou construire des expressions.
Le problème : le JSON ordinaire ne suffit pas
Le JSON a exactement trois sortes de scalaires — chaîne, nombre, booléen — plus null, tableaux
et objets. DynamoDB en a plus : le binaire, et trois types de set (set de chaînes, set de
nombres, set de binaires) que le JSON ne peut pas exprimer du tout. Et parce que les nombres
DynamoDB circulent sur le fil comme des chaînes, un 3 sans balise serait ambigu — de plus
le JSON ne peut pas distinguer une liste d'un set.
Alors DynamoDB ne peut pas simplement stocker ton JSON tel quel — il a besoin que le type exact de chaque valeur soit énoncé explicitement. Le descripteur de type est sa façon de le faire, sans perte, sur chaque requête et réponse.
Comment fonctionne l'encodage
Chaque valeur d'attribut devient un objet à une seule clé dont la clé est un descripteur de type :
| Descripteur | Type | Exemple |
|---|---|---|
S | Chaîne | {"S": "open"} |
N | Nombre (en chaîne) | {"N": "3"} |
B | Binaire | {"B": "dGV4dA=="} |
BOOL | Booléen | {"BOOL": true} |
NULL | Null | {"NULL": true} |
L | Liste | {"L": [{"S": "a"}, {"N": "1"}]} |
M | Map | {"M": {"k": {"S": "v"}}} |
SS / NS / BS | Set de chaînes / nombres / binaires | {"SS": ["a", "b"]} |
Les listes et maps imbriquent les mêmes descripteurs jusqu'au bout, donc un item profondément structuré devient profondément enveloppé. Les nombres circulent sur le fil comme des chaînes exprès — ça permet à DynamoDB de préserver ses 38 chiffres complets de précision numérique qu'un nombre JSON (un double IEEE-754, ~15–17 chiffres significatifs) arrondirait silencieusement. Ce sont les mêmes types de données que tu modélises ; le DynamoDB JSON n'est que leur forme filaire explicite, définie dans la référence de l'API bas niveau d'AWS.
Exemple concret : une entrée de journal d'audit
Le JSON ordinaire que tu écrirais dans ton app :
{
"actor": "u-204",
"action": "ticket.close",
"ticketId": 8842,
"tags": ["billing", "urgent"],
"redacted": false
}Marshallé en DynamoDB JSON pour l'API :
{
"actor": {"S": "u-204"},
"action": {"S": "ticket.close"},
"ticketId": {"N": "8842"},
"tags": {"SS": ["billing", "urgent"]},
"redacted": {"BOOL": false}
}Note les choix derrière cet item : ticketId est devenu N avec une valeur chaîne ;
tags en set de chaînes (SS), pas en liste, est un choix de modélisation fait à la
main — un convertisseur générique nourri de JSON ordinaire émet L, parce qu'un tableau JSON
est ordonné et peut se répéter, alors que SS déduplique et est non ordonné. Que tags
doive être SS ou L est une décision de modélisation que le convertisseur ne peut pas
faire à ta place, ce qui est exactement pourquoi comprendre l'encodage importe.
Convertir dans DynoTable
Tu as rarement besoin de lire ou d'écrire ça à la main. Colle du JSON ordinaire dans le convertisseur DynamoDB JSON pour le marshaller (et inversement), et quand tu assembles une requête, l' Expression Builder DynamoDB émet la map de valeurs d'attributs correctement marshallée à côté de l'expression. Dans l'app elle-même, DynoTable affiche les items comme des valeurs ordinaires et lisibles et les marshalle pour toi à l'écriture.

Pièges + étapes suivantes
- Les nombres sont des chaînes en DynamoDB JSON —
{"N": "3"}. Les guillemets comptent ; n'émets pas un nombre nu. - Set vs liste est une décision de modélisation que l'encodage rend visible — choisis délibérément (voir types de données).
- Préfère le document client du SDK au marshalling manuel dans le code applicatif ; réserve le DynamoDB JSON manuel au débogage et aux expressions.
- Les chaînes vides sont autorisées pour les attributs non-clés (depuis 2020) mais restent rejetées pour les clés de table et d'index, et ont historiquement fait trébucher l'outillage — valide les cas limites.
Tu veux parcourir les items comme des valeurs ordinaires au lieu de décoder les balises de type à l'œil ? Télécharge DynoTable et travaille avec tes données directement.
Client bas niveau vs document client
Le SDK AWS offre deux couches :
| Couche | Forme d'entrée | Qui marshalle |
|---|---|---|
@aws-sdk/client-dynamodb (bas niveau) | Maps DynamoDB JSON AttributeValue | Ton code ou un helper |
@aws-sdk/lib-dynamodb (document) | Objets JS ordinaires | Le SDK à l'envoi/réception |
Le code applicatif devrait par défaut utiliser le document client pour PutItem/GetItem. Recours aux maps bas niveau quand tu écris à la main des
expressions de mise à jour ou quand une librairie attend des valeurs d'attribut typées.
Les ExpressionAttributeValues sont aussi marshallées
Les placeholders de ConditionExpression, UpdateExpression et FilterExpression (:val, :inc) mappent vers des valeurs marshallées dans ExpressionAttributeValues :
":status": {"S": "open"}
":count": {"N": "1"}Un décalage — envoyer "open" sans le wrapper S sur le client bas niveau — renvoie ValidationException. L'
expression builder émet la map à côté de la chaîne d'expression pour que placeholders et types restent alignés.
Les noms d'attributs qui entrent en collision avec des
mots réservés utilisent ExpressionAttributeNames (#st) à la place ; l'outil checker sort la map d'alias prête à coller.
Surprises d'unmarshal dans les tests
Échecs de test fréquents liés au marshalling :
- Ensembles vides — DynamoDB rejette les
SS/NS/BSvides ; omets l'attribut à la place. - Floats dans
N— envoie"3.14"comme chaîne, pas comme nombre JSON, sur le wire. - Binaire en Node —
Uint8Arraydans le document client ; base64 dans le JSON brut. - Attributs undefined — le document client strip
undefined; le client bas niveau peut envoyer des payloads invalides.
Quand une Lambda journalise des réponses API brutes, colle un item dans le convertisseur DynamoDB JSON vers du JSON ordinaire lisible avant de differ contre des fixtures.
Impact taille du tagging
Chaque wrapper de type ajoute des octets. Un objet JSON plat marshallé champ par champ grossit d'environ 30–40 % sur le wire selon les noms d'attributs — cette inflation alimente la
taille d'item et l'arrondi RCU/WCU. Les grandes maps avec de courts noms d'attributs amortissent le surcoût ; les petits drapeaux booléens paient toujours leurs noms de clé plus {"BOOL":true}.
Avant de charger en masse des items marshallés, vérifie le total d'octets dans le calculateur de taille d'item pour qu'une écriture par lot ne croise pas inopinément la limite de requête de 16 Mo.
Les deux vues de DynoTable
L'éditeur d'item garde le marshalling invisible au quotidien — tu édites des valeurs ordinaires, et les commits marshallent à l'envoi. Quand tu débogues un item de production copié depuis des logs CloudWatch, passe en vue DynamoDB JSON pour voir les tags exacts, puis reviens au Plain JSON pour les éditions. Les actions d'export copient l'une ou l'autre représentation pour les tickets et les cas de test.


