DynamoDB JSON & Marshalling
Wenn du zum ersten Mal Rohdaten aus der DynamoDB-API liest, sieht das nicht aus wie das JSON,
das du hineingegeben hast. Ein schlichtes Objekt wie {"status": "open", "priority": 3} kommt
als {"status": {"S": "open"}, "priority": {"N": "3"}} zurück. Jeder Wert steckt in einem
Objekt mit genau einem Schlüssel, der seinen Typ benennt. Diese Verpackung ist DynamoDB
JSON, und die Umwandlung hin und zurück heißt Marshalling.
Diese Verpackung ist der Weg, auf dem DynamoDB die Typen auf der Leitung eindeutig hält. Sie bringt aber jeden aus dem Tritt, der schlichtes JSON erwartet, und von Hand geschrieben ist sie fehleranfällig.
Was ist DynamoDB JSON?
DynamoDB JSON ist das typmarkierte Wire-Format von DynamoDB, in dem jeder Wert in einem Objekt mit einem einzigen Schlüssel steckt, der seinen Typ benennt — {"S": "open"} für einen String, {"N": "3"} für eine Zahl. Schlichtes JSON dorthin (und zurück) zu wandeln heißt Marshalling. Es hält die Typen eindeutig, denn schlichtes JSON kann weder Sets noch Binary ausdrücken, und weil DynamoDB-Zahlen als Strings über die Leitung gehen, wäre eine untypisierte 3 mehrdeutig.
- DynamoDB JSON markiert jeden Wert mit seinem Typ —
{"S": "..."}für einen String,{"N": "..."}für eine Zahl und so weiter. - Marshalling = schlichtes JSON → DynamoDB JSON. Unmarshalling = umgekehrt.
- Zahlen sind auf der Leitung Strings —
{"N": "3"}, nicht{"N": 3}— um die Präzision zu erhalten. - Die Typ-Tags sind das Datentypsystem, mit dem du ohnehin modellierst: S, N, B, BOOL, NULL, L, M, SS, NS, BS.
- Schreib es nicht von Hand. Der Document Client des SDK (oder ein Konverter) marshallt für dich; von Hand machst du das nur beim Debuggen oder beim Bauen von Expressions.
Das Problem: schlichtes JSON reicht nicht
JSON kennt genau drei Skalar-Arten — String, Zahl, Boolean — plus null, Arrays und Objekte.
DynamoDB kennt mehr: Binary und drei Set-Typen (String Set, Number Set, Binary Set), die
JSON überhaupt nicht ausdrücken kann. Und weil DynamoDB-Zahlen als Strings über die Leitung
gehen, wäre eine untypisierte 3 mehrdeutig — außerdem kann JSON eine Liste nicht von einem
Set unterscheiden.
DynamoDB kann dein JSON also nicht einfach so speichern — es braucht den exakten Typ jedes Werts explizit. Der Typ-Deskriptor ist die Art, wie es das verlustfrei tut, bei jedem Request und jeder Response.
Wie die Kodierung funktioniert
Jeder Attributwert wird zu einem Objekt mit genau einem Schlüssel, und dieser Schlüssel ist ein Typ-Deskriptor:
| Deskriptor | Typ | Beispiel |
|---|---|---|
S | String | {"S": "open"} |
N | Zahl (als String) | {"N": "3"} |
B | Binary | {"B": "dGV4dA=="} |
BOOL | Boolean | {"BOOL": true} |
NULL | Null | {"NULL": true} |
L | Liste | {"L": [{"S": "a"}, {"N": "1"}]} |
M | Map | {"M": {"k": {"S": "v"}}} |
SS / NS / BS | String-, Number-, Binary-Set | {"SS": ["a", "b"]} |
Listen und Maps verschachteln dieselben Deskriptoren bis ganz nach unten, ein tief strukturiertes Item wird also tief verpackt. Zahlen gehen mit Absicht als Strings über die Leitung — so behält DynamoDB seine vollen 38 Stellen numerischer Präzision, die eine JSON-Zahl (ein IEEE-754-Double, ~15–17 signifikante Stellen) still wegrunden würde. Das sind dieselben Datentypen, mit denen du modellierst; DynamoDB JSON ist nur ihre explizite Form auf der Leitung, definiert in der AWS low-level API reference.
Durchgerechnetes Beispiel: ein Audit-Log-Eintrag
Das schlichte JSON, das du in deiner App schreiben würdest:
{
"actor": "u-204",
"action": "ticket.close",
"ticketId": 8842,
"tags": ["billing", "urgent"],
"redacted": false
}Für die API zu DynamoDB JSON marshallt:
{
"actor": {"S": "u-204"},
"action": {"S": "ticket.close"},
"ticketId": {"N": "8842"},
"tags": {"SS": ["billing", "urgent"]},
"redacted": {"BOOL": false}
}Beachte die Entscheidungen hinter diesem Item: ticketId wurde zu N mit einem
String-Wert; tags als String Set (SS) statt als Liste ist eine bewusste
Modellierungsentscheidung — ein generischer Konverter, dem du schlichtes JSON gibst, liefert
L, denn ein JSON-Array ist geordnet und darf sich wiederholen, während SS dedupliziert und
ungeordnet ist. Ob tags ein SS oder ein L sein soll, ist eine Modellierungsfrage, die dir
der Konverter nicht abnehmen kann — genau deshalb lohnt es sich, die Kodierung zu verstehen.
Konvertieren in DynoTable
Von Hand lesen oder schreiben musst du das selten. Füg schlichtes JSON in den DynamoDB-JSON-Konverter ein, um es zu marshallen (und zurück), und wenn du einen Request zusammenbaust, gibt der DynamoDB Expression Builder die korrekt marshallte Attributwert-Map gleich neben der Expression aus. In der App selbst zeigt DynoTable Items als schlichte, lesbare Werte und marshallt sie beim Schreiben für dich.

Fallstricke + nächste Schritte
- Zahlen sind in DynamoDB JSON Strings —
{"N": "3"}. Die Anführungszeichen zählen; gib keine nackte Zahl aus. - Set vs. Liste ist eine Modellierungsentscheidung, die die Kodierung sichtbar macht — triff sie bewusst (siehe Datentypen).
- Nimm im Anwendungscode lieber den Document Client als Marshalling von Hand; heb dir manuelles DynamoDB JSON fürs Debuggen und für Expressions auf.
- Leere Strings sind für Nicht-Schlüssel-Attribute erlaubt (seit 2020), für Tabellen- und Index-Schlüssel aber weiterhin abgelehnt, und sie haben historisch Tooling zum Stolpern gebracht — prüf die Randfälle.
Du willst Items als schlichte Werte durchsehen, statt Typ-Tags mit dem Auge zu dekodieren? Lade DynoTable herunter und arbeite direkt mit deinen Daten.
Low-Level-Client vs. Document Client
Das AWS SDK bietet zwei Schichten:
| Schicht | Eingabeform | Wer marshallt |
|---|---|---|
@aws-sdk/client-dynamodb (Low-Level) | DynamoDB-JSON-AttributeValue-Maps | Dein Code oder ein Helfer |
@aws-sdk/lib-dynamodb (Document) | Schlichte JS-Objekte | Das SDK beim Senden/Empfangen |
Anwendungscode sollte für PutItem/GetItem standardmäßig den Document Client nutzen.
Zu Low-Level-Maps greifst du, wenn du
Update Expressions selbst schreibst oder eine Bibliothek
typisierte Attributwerte erwartet.
Expression Attribute Values werden ebenfalls marshallt
Die Platzhalter in ConditionExpression, UpdateExpression und FilterExpression
(:val, :inc) verweisen auf marshallte Werte in ExpressionAttributeValues:
":status": {"S": "open"}
":count": {"N": "1"}Passt das nicht zusammen — etwa "open" ohne den S-Wrapper an den Low-Level-Client —, kommt
eine ValidationException zurück. Der
Expression Builder gibt die Map neben dem
Expression-String aus, damit Platzhalter und Typen im Gleichschritt bleiben.
Attributnamen, die mit
reservierten Wörtern kollidieren, laufen stattdessen
über ExpressionAttributeNames (#st); das Checker-Tool gibt die Alias-Map fertig zum
Einfügen aus.
Marshalling-Überraschungen in Tests
Häufige Testfehler rund ums Marshalling:
- Leere Sets — DynamoDB lehnt leere
SS/NS/BSab; lass das Attribut stattdessen weg. - Fließkommazahlen in
N— schick"3.14"als String, nicht als JSON-Zahl, über die Leitung. - Binary in Node —
Uint8Arrayim Document Client; base64 im rohen JSON. - Undefinierte Attribute — der Document Client wirft
undefinedweg; der Low-Level-Client kann ungültige Payloads schicken.
Wenn ein Lambda rohe API-Responses loggt, füg ein Item in den DynamoDB-JSON-Konverter ein, um lesbares schlichtes JSON zu bekommen, bevor du gegen Fixtures diffst.
Größenwirkung des Taggings
Jeder Typ-Wrapper kostet Bytes. Ein flaches JSON-Objekt, Feld für Feld marshallt, wächst auf
der Leitung je nach Attributnamen um grob 30–40 % — diese Aufblähung geht in die
Item-Größe und in die RCU-/WCU-Rundung ein. Große Maps mit
kurzen Attributnamen amortisieren den Overhead; winzige Boolean-Flags zahlen weiterhin für
ihren Schlüsselnamen plus {"BOOL":true}.
Bevor du marshallte Items massenweise lädst, prüf die Gesamtbytes im Item-Size-Rechner, damit ein Batch-Write nicht unerwartet die 16-MB-Request-Grenze reißt.
Die zwei Ansichten in DynoTable
Der Item-Editor hält Marshalling im Alltag unsichtbar — du bearbeitest schlichte Werte, und beim Commit wird beim Senden marshallt. Wenn du ein Produktions-Item debuggst, das du aus CloudWatch-Logs kopiert hast, schaltest du in die DynamoDB-JSON-Ansicht, um die exakten Tags zu sehen, und für Änderungen wieder zurück auf Plain JSON. Export-Aktionen kopieren beide Darstellungen für Tickets und Testfälle.


