Here’s a problem you’ll hit sooner or later if you work with JSON long enough: you need to send an image, a file, or some other chunk of binary data inside a JSON payload. And JSON has no way to do that. None. It only knows strings, numbers, booleans, null, objects, and arrays — nothing for raw bytes. So how does anyone actually get a picture or a PDF into a JSON API request? Base64 encoding. Let’s get into how it actually works, and what it costs you — with real measurements, not just the theory.

Why JSON Can’t Just Hold Binary Data

We tried the obvious thing first, just to show you what happens:

import json

binary_data = b'\x89PNG\r\n...'  # some raw bytes

json.dumps({'data': binary_data})

# TypeError: Object of type bytes is not JSON serializable

That error is real — we ran it, and that’s the exact message Python gives you. JSON strings are text — sequences of Unicode characters — and raw binary data isn’t text. It’s a stream of bytes that might contain sequences that aren’t valid text at all. JSON’s full list of data types is: string, number, boolean, null, object, array. That’s it. No “binary” type anywhere on that list, and there never will be, because JSON was designed as a text format from the ground up.

The Fix: Base64 Turns Bytes Into Text

Base64 solves this by converting binary data into a string made up of only 64 safe, printable characters (A-Z, a-z, 0-9, plus + and /). Once your data is base64 text, it’s just… a string. JSON has no problem with that at all.

import base64, json

binary_data = b'some binary content here'
encoded = base64.b64encode(binary_data).decode('ascii')

payload = json.dumps({'filename': 'chunk.bin', 'data': encoded})

print(payload)

And getting it back is just as simple — decode the base64, and you have your original bytes again:

parsed = json.loads(payload)
original = base64.b64decode(parsed['data'])

Tested: a real round trip, byte for byte

We ran this round trip on 1,024 bytes of genuinely random binary data — not a text string pretending to be binary — to make sure nothing gets corrupted along the way:

round-trip decoded == binary_data: True
sha256 original: 77bdcf17b780dfa9 | sha256 decoded: 77bdcf17b780dfa9
payload length: 1405 chars; base64 field: 1368 chars

The decoded bytes match the original byte for byte — same SHA-256 hash, not just the same length. The full JSON payload for that 1 KB file is 1,405 characters, of which 1,368 are the base64 data itself and the rest is the filename field and the JSON punctuation around it.

The Catch: It’s Not Free

Here’s the part people forget until it bites them. Base64 makes your data bigger. Noticeably bigger.

We measured it directly: 300 bytes of binary data became 400 characters of base64 text. That’s a 33.3% increase, and it’s not a fluke of our specific test — it’s just how the math works. Base64 groups every 3 bytes of input into 4 output characters, so you’re always looking at roughly a third more data, every single time.

Tested: the 33% tax at real file sizes

We encoded random binary data at six sizes, from 300 bytes up to a 5 MB phone photo, and measured the actual base64 output:

Original sizeBase64 sizeOverhead
300 bytes400 characters33.33%
1 KB (1,024 bytes)1,368 characters33.59%
100 KB (100,000 bytes)133,336 characters33.34%
1 MB (1,000,000 bytes)1,333,336 characters (1.33 MB of text)33.33%
1 MiB (1,048,576 bytes)1,398,104 characters (1.33 MiB of text)33.33%
5 MB (5,000,000 bytes — a phone photo)6,666,668 characters (6.67 MB of text)33.33%

The overhead is a flat 33.33% at every size — the one exception is the 1 KB row at 33.59%, and that’s just padding: 1,024 bytes doesn’t divide evenly by 3, so the encoder adds = padding characters to fill out the last group. At any real file size that extra padding is a rounding error.

Put it in concrete terms: a 1 MB image doesn’t travel as 1 MB of JSON. It travels as 1.33 MB of text. A 5 MB phone photo becomes 6.67 MB of JSON payload — and that’s before the rest of your request (headers, other fields) is added on top. For a small file, that’s nothing. For a large one, it adds up fast, and it’s worth knowing before you architect an API around embedding big files as base64 JSON fields.

When Base64-in-JSON Makes Sense

  • Small images or icons, especially where a separate file upload/download round trip would be more overhead than the size increase itself
  • Data URIs, where you’re embedding a small image directly into HTML or CSS and don’t want a separate network request
  • JWTs, which use base64url encoding for their header and payload
  • Anywhere a strict JSON-only API contract matters more than raw efficiency — some systems just won’t accept multipart form data or binary uploads, and base64-in-JSON is the workaround

When It Doesn’t

If you’re moving anything reasonably large — a photo from a phone camera, a video, a big PDF — base64-in-JSON is usually the wrong call. A dedicated file upload endpoint (multipart form data, or a signed URL straight to cloud storage) skips the 33% tax entirely and is what most real-world APIs actually do for anything beyond a few kilobytes. A 5 MB photo sent as base64 is 6.67 MB on the wire; sent as a file upload, it’s 5 MB.

A Quick Gotcha: Standard vs. URL-Safe Base64

Regular base64 uses + and / in its output, and both of those characters mean something special in a URL. If your base64 string is ever going to end up in a URL (a query parameter, part of a path), use the URL-safe variant instead, which swaps those two characters out:

url_safe = base64.urlsafe_b64encode(binary_data).decode('ascii')

This is exactly what JWTs use, for exactly this reason — a JWT gets passed around in headers and sometimes URLs, so it needs to survive that context without anything getting mangled or needing extra escaping.

Tested: the difference, character for character

We encoded the same 6 bytes (fb ff fe fb ef be) both ways, so you can see exactly which characters change:

demo bytes fbfffefbefbe -> standard: +//+++++
demo bytes fbfffefbefbe -> urlsafe:  -__-----

Every + becomes - and every / becomes _. Nothing else changes — same length, same data, and it decodes back to the exact same bytes:

urlsafe no padding: -__-----
decode back: fbfffefbefbe

One more detail worth knowing: URL-safe base64 is often written without the trailing = padding (JWTs do this). That shorter form is still valid — you just add the padding back before decoding, as the run above shows.

Checking the JSON Once You’ve Embedded Base64

One thing worth knowing: base64 strings are long, and they’re all one unbroken chunk of text with no spaces. That makes them easy to accidentally cut off when you’re copying a payload around by hand, or pasting it somewhere for a quick test. If a base64-heavy JSON payload isn’t validating and you can’t spot why, it’s worth running it through our JSON Formatter and Validator — a truncated base64 string usually shows up as a plain old unterminated-string error, which is easy to miss when the string itself is 400 characters of what looks like random noise. At the sizes in the table above, a truncated string could be over a million characters of noise.

Frequently Asked Questions

Does base64 encoding add any security or encryption?

No, not even a little. It’s purely a format conversion — anyone can decode it instantly with no key or password needed. Don’t mistake it for a way to hide or protect data.

Can I base64 encode a whole file, or just small chunks?

Either works technically. The practical limit is really about payload size and your API’s request size limits, not base64 itself. A 5 MB file encodes fine — it just becomes 6.67 MB of JSON, which may exceed a request-size limit that the raw 5 MB file would have passed.

Why does my base64 string end with one or two = signs?

That’s padding. Base64 works in groups of 3 bytes, and when your data doesn’t divide evenly into groups of 3, = characters get added at the end to fill out the last group. Totally normal, not an error. Our 1 MB test file (1,048,576 bytes) ended in WA== for exactly this reason.

Is there a more efficient way to embed binary data in JSON?

Not within standard JSON itself — base64 is really the accepted way to do it. If efficiency matters more than JSON compatibility, a binary format like Protocol Buffers or MessagePack skips this overhead entirely, but that’s a different format, not a JSON trick.

How much bigger does a 1 MB file actually get?

Exactly a third bigger, measured: 1,000,000 bytes becomes 1,333,336 characters of base64 (1.33 MB of text), and 1,048,576 bytes (1 MiB) becomes 1,398,104 characters. A 5 MB phone photo becomes 6.67 MB of JSON text.

Summary

Base64 is the standard way to squeeze binary data into JSON’s text-only world, and it works reliably — we verified the round trip byte for byte (matching SHA-256 hashes) and measured the size cost at six file sizes. Just remember it’s not free: you’re paying about a 33% size tax every time, confirmed by our own tests — a 1 MB file travels as 1.33 MB of text, a 5 MB photo as 6.67 MB. Fine for small stuff like icons, data URIs, and JWTs. Not something you want to reach for when you’re moving anything genuinely large — use a file upload endpoint instead, and skip the tax entirely.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *