JSON Payload Size: How to Measure It (and Shrink It)

Why .length lies about your payload size, the limits you'll actually hit, and the three changes that genuinely shrink JSON.

The first time a JSON payload bit me, it was a 413 Payload Too Large in production — a perfectly valid API response that nginx refused to pass through. The second time it was an AWS Lambda silently failing because the response crept past its limit. Both times I'd never actually measured the JSON; I'd eyeballed it and assumed "that's small enough." It wasn't. If you work with APIs, here's how to stop guessing.

Why .length lies — size is measured in bytes

The most common mistake is checking JSON.stringify(obj).length. That returns the character count, not the byte size — and they're not the same. JSON travels over the wire as UTF-8, where any non-ASCII character takes 2–4 bytes. "José" is 4 characters but 5 bytes (the é is two). A response full of emoji, accented names, or CJK text can be meaningfully larger than its character count suggests.

Measure the real thing:

// Node.js — actual UTF-8 byte size
const json = JSON.stringify(payload);
const bytes = Buffer.byteLength(json, "utf8");
console.log(`${bytes} bytes  (${(bytes / 1024).toFixed(1)} KB)`);
# Python — actual UTF-8 byte size
import json
data = json.dumps(payload)
size = len(data.encode("utf-8"))
print(f"{size} bytes  ({size/1024:.1f} KB)")

The limits you'll actually hit

You don't need to optimize JSON in the abstract — you need to stay under the wall in front of you. The common ones:

LayerDefault limit
Express express.json()100 KB (body-parser default)
nginx client_max_body_size1 MB default
AWS Lambda (synchronous)6 MB request/response
AWS API Gateway10 MB

The Express 100 KB default catches people constantly — a body just over it returns a 413 and most devs never realize the limit was that low. So know your gateway's number, and measure against it before you ship.

Find what's actually bloating it

Total size tells you if you have a problem; size-by-key tells you where it is. In almost every oversized payload I've debugged, one of four things was the culprit: a base64 blob, verbose key names repeated across thousands of array items, pretty-print whitespace, or a pile of null/empty fields.

import json

def size_by_key(obj):
    return {
        k: len(json.dumps(v).encode("utf-8"))
        for k, v in obj.items()
    }

for k, b in sorted(size_by_key(payload).items(), key=lambda x: -x[1]):
    print(f"{b:>8} bytes  {k}")

Run that and the bloat usually jumps out — it's rarely spread evenly.

The three changes that move the needle

Not all "optimizations" are equal. Here's what actually helps, roughly in order of impact:

TechniqueEffect
gzip / brotliThe biggest win by far. JSON is highly repetitive (keys repeat every row), so it compresses extremely well — a typical response shrinks ~70–90% once Content-Encoding: gzip is on.
Minify (drop whitespace)Pretty-printing with 2-space indent adds bytes proportional to nesting and row count. Send minified JSON to the wire; keep pretty-print for logs.
Trim the shapeDrop null/empty fields, paginate big arrays, and return only the fields the client needs. Structural, but durable.

A note on ordering: people reach for shortening key names first, but if you're already gzipping, repeated keys compress so well that renaming them barely helps. Turn on compression first — it's usually a one-line gateway change and dwarfs everything else.

A faster way to check (no script)

When I just want a number — is this response a problem or not? — writing a measurement script is overkill. I built a free, in-browser tool for exactly this: paste the JSON and it shows the size in bytes, KB, and MB plus the biggest keys, all client-side with nothing uploaded — the JSON Size Calculator & Analyzer. If it flags a payload as too big, minifying it is the fastest first cut.

FAQs

Is the measured size the same as what's sent over the network?
No — if you gzip (and you should), the Content-Length on the wire is the compressed size, often 70–90% smaller than the raw JSON. Measure raw size for limits like Lambda's payload cap, but transfer size is the compressed figure.

Does minifying change my data?
No. Minifying only removes whitespace (indentation and line breaks). Values, types, and key order are untouched.

Why is my size bigger than the character count?
UTF-8. Any non-ASCII character (accents, emoji, CJK) is 2–4 bytes, so byte size ≥ character count. Always measure bytes, not .length.

What's a "safe" JSON payload size?
There's no universal number — it's whatever your smallest gateway allows (often 100 KB–10 MB). Find that limit, measure your real payloads against it, and leave headroom.

Related articles