JSON request bodies test values

JSON request bodies that parsers and databases read differently. Rename the example fields to those of your API. 12 values, each with what it breaks and what a correct application does instead.

json-payloads version 1.0, updated 2026-11-03 CC-BY-4.0

Type them one at a time with AltShiftN in the palette, or take them all into a script with nkb emit json-payloads.

Empty request body

json-payloads/empty-body
  • 0 graphemes
  • 0 code points
  • 0 bytes
  • 0 UTF-16 units
What it breaks
An empty body is not JSON; most parsers raise an error and some return no value at all, so a handler written for one of the two answers 500 instead of 400.
What a correct application does
400 with a message that says the body is empty.

Duplicate key

json-payloads/duplicate-key
{"quantity":1,"quantity":100} {"quantity":1,"quantity":100}
  • 29 graphemes
  • 29 code points
  • 29 bytes
  • 29 UTF-16 units
What it breaks
RFC 8259 leaves duplicate names undefined: most parsers keep the last value and a few refuse the body, so two layers with different parsers can each see a different quantity.
What a correct application does
Rejected with 400, or the same value used by every layer that reads the body.

Integer above 2^53

json-payloads/big-int-id
{"id":9007199254740993} {"id":9007199254740993}
  • 23 graphemes
  • 23 code points
  • 23 bytes
  • 23 UTF-16 units
What it breaks
Above 2^53 a double cannot hold every integer: JavaScript and a Go map read 9007199254740993 as 9007199254740992, while a typed int64 keeps it, so an id changes between two services.
What a correct application does
Ids above 2^53 - 1 sent as strings, or rejected with a message.

Explicit null

json-payloads/explicit-null
{"name":null} {"name":null}
  • 13 graphemes
  • 13 code points
  • 13 bytes
  • 13 UTF-16 units
What it breaks
A typed model reads null and a missing field as the same empty value, so a partial update cannot tell a request to clear the name from one that leaves it alone.
What a correct application does
Null and an absent field handled as two different requests, the way the API documents them.

Number sent as a string

json-payloads/number-as-string
{"quantity":"5"} {"quantity":"5"}
  • 16 graphemes
  • 16 code points
  • 16 bytes
  • 16 UTF-16 units
What it breaks
Some binders read the string 5 as the number 5 and others refuse it, so the same body passes one service and is rejected by the next one in the chain.
What a correct application does
Rejected with 400 naming the field and its type, or converted the same way in every layer.

1001 nested arrays

json-payloads/nesting-1001
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] 2,002 code points in all, cut in the middle [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]
  • 2,002 graphemes
  • 2,002 code points
  • 2,002 bytes
  • 2,002 UTF-16 units
What it breaks
Each parser stops at its own depth, from 64 levels to 1000 or none at all, so a body that one layer parses another rejects with a depth error rather than a syntax error.
What a correct application does
Rejected with 400 at a documented depth, before any handler sees it.

Escaped lone surrogate

json-payloads/lone-surrogate
{"name":"\ud800"} {"name":"\\ud800"}
  • 17 graphemes
  • 17 code points
  • 17 bytes
  • 17 UTF-16 units
What it breaks
An escaped lone surrogate is valid JSON syntax but not valid text: some parsers refuse it, Go encoding/json replaces it with U+FFFD, and others keep it until writing it as UTF-8 fails or changes it.
What a correct application does
Rejected with 400, or replaced visibly before the value is stored.

Escaped NUL in a string

json-payloads/escaped-nul
{"name":"a\u0000b"} {"name":"a\\u0000b"}
  • 19 graphemes
  • 19 code points
  • 19 bytes
  • 19 UTF-16 units
What it breaks
JSON parsers keep the NUL inside the string, so it reaches storage: PostgreSQL jsonb refuses it, and code that uses C strings cuts the name after the a.
What a correct application does
Rejected with 400 at the API, or the NUL removed before the value is stored.

Number beyond a double

json-payloads/huge-exponent
{"price":1e400} {"price":1e400}
  • 15 graphemes
  • 15 code points
  • 15 bytes
  • 15 UTF-16 units
What it breaks
1e400 is valid JSON and beyond a double: Go and serde refuse it, Python, .NET and Jackson read infinity, JavaScript writes it back as null, and PostgreSQL jsonb keeps all 401 digits.
What a correct application does
Rejected with 400 as out of range for the field.

Keys differing only in case

json-payloads/case-variant-keys
{"quantity":1,"Quantity":100} {"quantity":1,"Quantity":100}
  • 29 graphemes
  • 29 code points
  • 29 bytes
  • 29 UTF-16 units
What it breaks
Case-insensitive binding, the default in ASP.NET Core and in Go encoding/json, takes the second key, while case-sensitive binders take the first or refuse the unknown one, so layers disagree about the quantity.
What a correct application does
Rejected with 400, or one documented key used by every layer.

Trailing comma

json-payloads/trailing-comma
{"quantity":1,} {"quantity":1,}
  • 15 graphemes
  • 15 code points
  • 15 bytes
  • 15 UTF-16 units
What it breaks
JavaScript object literals and JSON5 allow a trailing comma and JSON does not, so a body written by hand or by a lenient client fails on a strict server, with 400 or with 500.
What a correct application does
400 with a message that points at the comma.

Byte order mark before the body

json-payloads/bom-before-body
{"quantity":1} \uFEFF{"quantity":1}
  • 1 zero-width character
  • 15 graphemes
  • 15 code points
  • 17 bytes
  • 15 UTF-16 units
What it breaks
RFC 8259 forbids a byte order mark before JSON and lets parsers ignore one: Jackson and decoders that strip it accept the body, while Go, serde and PostgreSQL refuse it, so one request passes one service and fails another.
What a correct application does
Accepted with the mark ignored, or rejected with 400 that names it; never 500.