Curated feed active · 3h sync cadence
Technical Guide · Data Formats & Security · RFC 8259 / RFC 4648

Safe Handling of JSON Serialization, UTF-8, and URL/Base64 Encoding

Data interchange across modern HTTP APIs, configuration manifests, and cryptographic tokens relies heavily on three standards: JSON (RFC 8259), UTF-8 (RFC 3629), and Base64 (RFC 4648). Subtle mismatches in how runtimes parse numbers, characters, and binary octets are a frequent source of production outages and security vulnerabilities.

1. RFC 8259 JSON Interoperability Pitfalls

While JSON syntax is intentionally minimal, RFC 8259 highlights several areas where parsers diverge or lose precision:

  • 64-Bit Integer Precision Loss (IEEE-754): JavaScript and browser JSON.parse() represent all JSON numbers as 64-bit IEEE-754 floating-point values, which only guarantee exact integer precision up to Number.MAX_SAFE_INTEGER (253 − 1 = 9,007,199,254,740,991). A 64-bit database ID or Snowflake timestamp such as 9007199254740993 will silently round to 9007199254740992! Always serialize 64-bit integers as JSON strings ("9007199254740993") when interoperating with browser clients.
  • Duplicate Object Keys: RFC 8259 states that object member names should be unique. When duplicate keys exist, some parsers keep the last key while others keep the first—enabling parser differential attacks against validation proxies.
  • Never Evaluate JSON as Code: Always parse JSON using a strict grammar parser (JSON.parse in JavaScript, json.loads in Python) and never pass untrusted strings to eval() or new Function().

2. Why Browser btoa() Fails on Unicode (and How to Fix It)

Base64 maps 3-byte (24-bit) groups of binary data into 4 ASCII characters. In browser JavaScript, the legacy window.btoa() function treats each UTF-16 code unit of a DOMString as a single byte (0–255). Passing any character outside Latin-1—such as "✓" (U+2713) or Cyrillic/CJK text—throws an immediate InvalidCharacterError.

The standards-compliant solution converts the string into a UTF-8 Uint8Array via TextEncoder before Base64 encoding, and decodes back via TextDecoder('utf-8', { fatal: true }):

// UTF-8 safe encoding pipeline
const utf8Bytes = new TextEncoder().encode("UTF-8 ✓ Δ");
// Decode with { fatal: true } to reject malformed byte sequences
const text = new TextDecoder("utf-8", { fatal: true }).decode(utf8Bytes);

3. Standard Base64 vs. Base64URL (RFC 4648 §5)

Standard Base64 (RFC 4648 §4) uses + at index 62, / at index 63, and = for padding. Because +, /, and = have reserved meanings in URI paths and query strings, placing standard Base64 directly into a URL corrupts data unless percent-encoded.

Specification 62nd Char 63rd Char Padding (=) Primary Use Cases
Base64 (RFC 4648 §4) + / Required PEM certificates, MIME email attachments, SRI hashes
Base64URL (RFC 4648 §5) - _ Omitted / Optional URL parameters, WebAuthn challenges, JWS/JWT segments

4. encodeURI vs. encodeURIComponent

When constructing URLs dynamically in JavaScript:

  • Use encodeURIComponent(value) for individual query parameter keys and values. It percent-encodes reserved URI delimiters including &, =, +, /, ?, and #.
  • Use encodeURI(fullUrl) only when normalizing a complete URI whose structural delimiters (https://host/path?a=b) must remain intact.

Open JSON Formatter → Open URL & Base64 Utility →