JSON Parsing in Python, JavaScript, Go, and Ruby: Edge Cases That Cause Real Bugs
Python's None vs JSON null, JavaScript's integer precision limit at 2^53, Go's strict struct tags, and Ruby's symbolize_names security concern are real production pitfalls. Here's the JSON parsing edge cases in each major language and how they cause bugs at API boundaries.
By sadiqbd Β· June 10, 2026
JSON parsing looks simple β until you hit the edge cases that differ between Python, JavaScript, Ruby, and Go
Every major programming language can parse JSON. The standard libraries work. But each language has its own JSON handling quirks β Python's None vs JSON's null, JavaScript's lack of integer type, Go's strict typing requiring predefined structs, Ruby's symbolized keys option, Java's verbose object mapping. These aren't minor differences. They're the source of subtle bugs that appear at language boundaries.
Python: json module pitfalls
Python's built-in json module is the standard approach.
The None/null conversion:
import json
# Python None becomes JSON null
json.dumps({"value": None})
# β '{"value": null}'
# JSON null becomes Python None
json.loads('{"value": null}')
# β {'value': None}
This is correct and expected. The confusion arises when code that works on Python objects encounters JSON null in an API response β None checks are required.
Integer vs float handling:
# Python integers become JSON integers
json.dumps({"count": 5})
# β '{"count": 5}'
# Python floats with integer values become JSON floats
json.dumps({"count": 5.0})
# β '{"count": 5.0}'
# JSON numbers can become either int or float in Python
json.loads('{"a": 1, "b": 1.5}')
# β {'a': 1, 'b': 1.5} β 1 stays int, 1.5 stays float
Unicode and encoding:
# Default: non-ASCII characters are escaped as \uXXXX
json.dumps({"name": "JosΓ©"})
# β '{"name": "Jos\\u00e9"}'
# To preserve non-ASCII characters:
json.dumps({"name": "JosΓ©"}, ensure_ascii=False)
# β '{"name": "JosΓ©"}'
For APIs serving international users, ensure_ascii=False is usually preferable β the output is smaller and more readable.
Datetime is not JSON-serialisable by default:
from datetime import datetime
json.dumps({"created": datetime.now()})
# β TypeError: Object of type datetime is not JSON serializable
# Solution: use isoformat() or a custom encoder
json.dumps({"created": datetime.now().isoformat()})
# β '{"created": "2024-06-09T14:30:00.123456"}'
# Or a custom default handler:
def json_serial(obj):
if isinstance(obj, datetime):
return obj.isoformat()
raise TypeError(f"Type {type(obj)} not serializable")
json.dumps({"created": datetime.now()}, default=json_serial)
JavaScript: the integer problem
JavaScript has no integer type β all numbers are IEEE 754 double-precision floats. This is fine for most integers, but 64-bit integers lose precision:
// The maximum safe integer in JavaScript
Number.MAX_SAFE_INTEGER === 9007199254740991 // 2^53 - 1
// Beyond this, integers lose precision
JSON.parse('{"id": 9007199254740993}')
// β {id: 9007199254740992} β WRONG, last digit changed
// This affects database IDs, Twitter Snowflake IDs, etc.
// The fix: receive large integers as strings
JSON.parse('{"id": "9007199254740993"}')
// β {id: "9007199254740993"} β correct as string
Twitter/X, Discord, Instagram all serve their IDs as strings in JSON for exactly this reason β JavaScript can't represent their 64-bit IDs accurately as numbers.
JSON.stringify with replacer:
// Transform values during serialisation
JSON.stringify({name: "Alice", password: "secret", age: 30},
(key, value) => key === 'password' ? undefined : value
)
// β '{"name":"Alice","age":30}' β password excluded
// Pretty print
JSON.stringify({a: 1, b: [1,2]}, null, 2)
// β '{
// "a": 1,
// "b": [
// 1,
// 2
// ]
// }'
JSON.parse with reviver:
// Transform values during parsing
JSON.parse('{"date":"2024-06-09","amount":100}',
(key, value) => key === 'date' ? new Date(value) : value
)
// β {date: Date object, amount: 100}
Go: strict typing with struct tags
Go requires either predefined struct types or map[string]interface{} for JSON parsing. The strict typing catches mismatches at unmarshal time.
type User struct {
ID int `json:"id"`
Email string `json:"email"`
CreatedAt time.Time `json:"created_at"`
Score *float64 `json:"score,omitempty"`
}
// Unmarshal into struct
var user User
err := json.Unmarshal([]byte(data), &user)
// Marshal from struct
bytes, err := json.Marshal(user)
Key Go JSON behaviours:
- Fields without
json:tags use the Go field name (capitalised) β often not what you want for APIs that use snake_case omitemptyomits the field from JSON if it's a zero value (0, "", false, nil)- Pointer types (
*float64) allow distinguishing zero value from absent field β anilpointer is omitted withomitempty; a pointer to 0.0 is included
Unknown fields:
// Default: unknown JSON fields are silently ignored
json.Unmarshal([]byte(`{"id":1,"unknown":"ignored"}`), &user)
// No error; "unknown" is discarded
// To fail on unknown fields:
d := json.NewDecoder(bytes.NewReader(data))
d.DisallowUnknownFields()
err := d.Decode(&user)
// Error if JSON contains fields not in the struct
Ruby: symbol vs string keys
Ruby's JSON.parse produces string keys by default. Using symbolize_names: true produces symbol keys β which has performance and memory implications:
require 'json'
# String keys (default)
JSON.parse('{"name":"Alice","age":30}')
# => {"name"=>"Alice", "age"=>30}
# Symbol keys
JSON.parse('{"name":"Alice","age":30}', symbolize_names: true)
# => {:name=>"Alice", :age=>30}
The symbolize_names security concern: symbols in Ruby are not garbage collected in the same way as strings. Creating many unique symbols (e.g., from user-controlled JSON key names) is a DoS vector β it can exhaust memory. For parsing untrusted JSON with potentially many unique keys, string keys are safer.
Common cross-language pitfalls at API boundaries
Float precision across languages:
A Python float serialised to JSON and parsed in JavaScript may lose precision in edge cases, because Python's JSON serialiser uses Python's float representation while JavaScript uses its own.
# Python
import json
json.dumps({"val": 1/3})
# β '{"val": 0.3333333333333333}'
Both languages use IEEE 754 doubles, so for most values this is fine. The danger zone is very large or very small numbers where floating-point representation differs.
Null vs undefined (JavaScript):
JSON has no undefined. JSON.stringify silently drops object properties with value undefined:
JSON.stringify({a: 1, b: undefined, c: 3})
// β '{"a":1,"c":3}' β b is gone
This means a value that exists in JavaScript as undefined disappears when serialised, which can be indistinguishable from the field never having existed.
How to use the JSON Unescape tool on sadiqbd.com
- Paste escaped or double-encoded JSON β the tool removes backslash escaping
- Use for cross-language debugging β Python's
json.dumpswith nested serialisation, or stringified JSON from any language, can produce escaped JSON that needs unescaping for readability - Decode Unicode escapes β
\u00e9βΓ©
Frequently Asked Questions
Why does JSON.stringify(undefined) return undefined (not a string)?
Calling JSON.stringify(undefined) on a bare undefined value (not a property) returns the JavaScript value undefined (not the string "undefined"). JSON has no way to represent undefined, so the result is not a valid JSON string. In practice, check that the value you're stringifying is not undefined before serialisation.
Is Python's json.dumps the right tool for all serialisation?
For standard use, yes. For complex object graphs, consider pydantic (with built-in JSON serialisation that handles types, validation, and schema generation) or orjson (much faster than standard library, with native support for numpy arrays, datetime, and UUID).
Is the JSON Unescape tool free? Yes β completely free, no sign-up required.
JSON parsing is standard in every language, but the edge cases β null handling, large integer precision, symbol vs string keys, undefined serialisation β diverge in ways that cause real bugs at language boundaries. Knowing your language's specific behaviours prevents the class of bugs that appear when APIs talk to each other.
Try the JSON Unescape tool free at sadiqbd.com β remove JSON escaping, decode Unicode sequences, and clean double-encoded JSON instantly.