JSON Parsing in Every Language: JavaScript, Python, Go, Java, and More
JSON (JavaScript Object Notation) is the universal data interchange format of modern software development. Whether you are building a REST API, reading a configuration file, or passing messages between microservices, you will need to parse JSON. Every mainstream programming language has a built-in or standard library for it, but the APIs, idioms, and pitfalls differ wildly from one language to the next.
This guide gives you production-ready JSON parsing patterns in eight languages: JavaScript, Python, Go, Java, PHP, Ruby, Rust, and C#. For each one we cover basic parsing and serialization, error handling, working with nested objects and arrays, and mapping JSON to custom types. After the language sections, we cover common pitfalls that apply everywhere and finish with performance tips.
1. JavaScript — JSON.parse and JSON.stringify
JavaScript has native JSON support built into every runtime (browsers, Node.js, Deno, Bun). The two core functions live on the global JSON object.
Basic Parsing and Serialization
// Parse a JSON string into a JavaScript object
const data = JSON.parse('{"name": "Alice", "age": 30}');
console.log(data.name); // "Alice"
// Serialize a JavaScript object into a JSON string
const json = JSON.stringify({ name: "Alice", age: 30 });
console.log(json); // '{"name":"Alice","age":30}'
// Pretty-print with 2-space indentation
const pretty = JSON.stringify(data, null, 2);
Error Handling
JSON.parse throws a SyntaxError if the input is not valid JSON. Always wrap it in a try/catch when parsing untrusted input:
function safeParse(text) {
try {
return { data: JSON.parse(text), error: null };
} catch (err) {
return { data: null, error: err.message };
}
}
const result = safeParse('{"broken: true}');
if (result.error) {
console.error("Invalid JSON:", result.error);
}
Nested Objects and Arrays
const response = JSON.parse(`{
"users": [
{"id": 1, "name": "Alice", "roles": ["admin", "editor"]},
{"id": 2, "name": "Bob", "roles": ["viewer"]}
],
"meta": {"total": 2, "page": 1}
}`);
// Access nested data
console.log(response.users[0].roles[1]); // "editor"
console.log(response.meta.total); // 2
// Optional chaining for safety
console.log(response.users?.[5]?.name); // undefined (no crash)
Reviver and Replacer Functions
The second argument to JSON.parse is a reviver that can transform values during parsing. JSON.stringify accepts a replacer as its second argument:
// Reviver: auto-convert ISO date strings to Date objects
const withDates = JSON.parse(
'{"created": "2026-02-11T10:00:00Z"}',
(key, value) => {
if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}T/.test(value)) {
return new Date(value);
}
return value;
}
);
console.log(withDates.created instanceof Date); // true
// Replacer: strip sensitive fields during serialization
const user = { name: "Alice", password: "secret", email: "a@b.com" };
const safe = JSON.stringify(user, (key, value) => {
if (key === "password") return undefined;
return value;
}, 2);
$.users[0].roles[1]).
2. Python — The json Module
Python's standard library includes the json module. The two main functions are json.loads (parse a string) and json.dumps (serialize to a string). For files, use json.load and json.dump (no trailing "s").
Basic Parsing and Serialization
import json
# Parse JSON string to Python dict
data = json.loads('{"name": "Alice", "age": 30}')
print(data["name"]) # Alice
# Serialize Python dict to JSON string
text = json.dumps({"name": "Alice", "age": 30})
print(text) # {"name": "Alice", "age": 30}
# Pretty-print
pretty = json.dumps(data, indent=2, sort_keys=True)
# Read from / write to a file
with open("data.json") as f:
data = json.load(f)
with open("output.json", "w") as f:
json.dump(data, f, indent=2)
Error Handling
Invalid JSON raises json.JSONDecodeError (a subclass of ValueError):
import json
raw = '{"broken: true}'
try:
data = json.loads(raw)
except json.JSONDecodeError as e:
print(f"Parse error at line {e.lineno}, col {e.colno}: {e.msg}")
# Parse error at line 1, col 16: Expecting ':' delimiter
Nested Objects and Arrays
import json
response = json.loads("""
{
"users": [
{"id": 1, "name": "Alice", "tags": ["admin", "editor"]},
{"id": 2, "name": "Bob", "tags": ["viewer"]}
],
"pagination": {"page": 1, "total_pages": 5}
}
""")
# Access nested data
print(response["users"][0]["tags"][1]) # editor
print(response["pagination"]["total_pages"]) # 5
# Safe access with .get()
print(response.get("missing_key", "default")) # default
Custom Types with object_hook
You can map JSON objects directly to Python classes:
import json
from dataclasses import dataclass
@dataclass
class User:
id: int
name: str
email: str
def user_hook(obj):
if "id" in obj and "name" in obj and "email" in obj:
return User(**obj)
return obj
data = json.loads(
'{"id": 1, "name": "Alice", "email": "alice@example.com"}',
object_hook=user_hook
)
print(isinstance(data, User)) # True
print(data.name) # Alice
# For serialization of custom objects, use a default function
def custom_encoder(obj):
if isinstance(obj, User):
return {"id": obj.id, "name": obj.name, "email": obj.email}
raise TypeError(f"Object of type {type(obj)} is not JSON serializable")
json.dumps(data, default=custom_encoder)
3. Go — encoding/json
Go's standard library provides encoding/json. Go's static type system means you either unmarshal into a concrete struct or into a generic map[string]interface{}.
Basic Parsing and Serialization
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email,omitempty"`
}
func main() {
// Parse JSON into a struct
raw := []byte(`{"name": "Alice", "age": 30}`)
var user User
err := json.Unmarshal(raw, &user)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(user.Name) // Alice
// Serialize struct to JSON
output, err := json.Marshal(user)
fmt.Println(string(output)) // {"name":"Alice","age":30}
// Pretty-print
pretty, _ := json.MarshalIndent(user, "", " ")
fmt.Println(string(pretty))
}
Error Handling
Go's json.Unmarshal returns an error rather than panicking. Check common error types:
raw := []byte(`{"broken: true}`)
var data map[string]interface{}
err := json.Unmarshal(raw, &data)
if err != nil {
// Check for specific error types
if syntaxErr, ok := err.(*json.SyntaxError); ok {
fmt.Printf("Syntax error at byte offset %d\n", syntaxErr.Offset)
} else if typeErr, ok := err.(*json.UnmarshalTypeError); ok {
fmt.Printf("Type mismatch for field %s: expected %s\n",
typeErr.Field, typeErr.Type)
} else {
fmt.Println("JSON error:", err)
}
}
Nested Objects and Dynamic JSON
// Nested structs map directly to nested JSON
type APIResponse struct {
Users []User `json:"users"`
Meta struct {
Total int `json:"total"`
Page int `json:"page"`
} `json:"meta"`
}
// For dynamic or unknown JSON, use map[string]interface{}
var dynamic map[string]interface{}
json.Unmarshal(raw, &dynamic)
// Type assertions required for nested access
users := dynamic["users"].([]interface{})
first := users[0].(map[string]interface{})
fmt.Println(first["name"]) // Alice
// json.RawMessage lets you defer parsing of a field
type Envelope struct {
Type string `json:"type"`
Payload json.RawMessage `json:"payload"`
}
var env Envelope
json.Unmarshal(raw, &env)
// Now decide how to unmarshal env.Payload based on env.Type
Streaming with json.Decoder
import "os"
// Decode from an io.Reader (file, HTTP body, etc.)
file, _ := os.Open("data.json")
defer file.Close()
decoder := json.NewDecoder(file)
var user User
err := decoder.Decode(&user)
// Encode to an io.Writer
encoder := json.NewEncoder(os.Stdout)
encoder.SetIndent("", " ")
encoder.Encode(user)
4. Java — Jackson and Gson
Java does not include a JSON parser in the core language. The two most popular libraries are Jackson (fasterxml) and Gson (Google). Both are mature, well-tested, and handle virtually every use case.
Jackson — Basic Parsing
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
ObjectMapper mapper = new ObjectMapper();
// Parse into a POJO
String json = "{\"name\": \"Alice\", \"age\": 30}";
User user = mapper.readValue(json, User.class);
System.out.println(user.getName()); // Alice
// Parse into a tree (JsonNode) for dynamic access
JsonNode root = mapper.readTree(json);
System.out.println(root.get("name").asText()); // Alice
System.out.println(root.get("age").asInt()); // 30
// Serialize to JSON
String output = mapper.writeValueAsString(user);
// Pretty-print
String pretty = mapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(user);
Jackson — POJO Definition
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true)
public class User {
private String name;
private int age;
@JsonProperty("email_address")
private String email;
// Constructors, getters, setters
public User() {}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
Jackson — Error Handling
import com.fasterxml.jackson.core.JsonProcessingException;
try {
User user = mapper.readValue(invalidJson, User.class);
} catch (JsonProcessingException e) {
System.err.println("JSON parse error: " + e.getOriginalMessage());
System.err.println("Location: line " + e.getLocation().getLineNr()
+ ", column " + e.getLocation().getColumnNr());
}
Jackson — Nested Objects, Arrays, and Generics
import com.fasterxml.jackson.core.type.TypeReference;
import java.util.List;
import java.util.Map;
// Parse an array of users
String jsonArray = "[{\"name\":\"Alice\"},{\"name\":\"Bob\"}]";
List<User> users = mapper.readValue(jsonArray,
new TypeReference<List<User>>() {});
// Parse a nested response wrapper
public class ApiResponse<T> {
private T data;
private Map<String, Object> meta;
// getters/setters
}
ApiResponse<List<User>> response = mapper.readValue(json,
new TypeReference<ApiResponse<List<User>>>() {});
Gson — Quick Comparison
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
Gson gson = new GsonBuilder().setPrettyPrinting().create();
// Parse
User user = gson.fromJson("{\"name\": \"Alice\", \"age\": 30}", User.class);
// Serialize
String json = gson.toJson(user);
// Parse a List
Type listType = new TypeToken<List<User>>(){}.getType();
List<User> users = gson.fromJson(jsonArray, listType);
// Error handling
try {
User u = gson.fromJson("not json", User.class);
} catch (JsonSyntaxException e) {
System.err.println("Gson error: " + e.getMessage());
}
5. PHP — json_decode and json_encode
PHP has had built-in JSON functions since version 5.2. The two primary functions are json_decode and json_encode.
Basic Parsing and Serialization
<?php
// Parse JSON string (returns object by default)
$data = json_decode('{"name": "Alice", "age": 30}');
echo $data->name; // Alice
// Parse as associative array (pass true as second argument)
$data = json_decode('{"name": "Alice", "age": 30}', true);
echo $data['name']; // Alice
// Serialize to JSON
$json = json_encode(['name' => 'Alice', 'age' => 30]);
echo $json; // {"name":"Alice","age":30}
// Pretty-print
$pretty = json_encode($data, JSON_PRETTY_PRINT);
// Useful flags
$json = json_encode($data,
JSON_PRETTY_PRINT |
JSON_UNESCAPED_SLASHES |
JSON_UNESCAPED_UNICODE
);
Error Handling
By default, json_decode returns null on failure and does not throw. Use JSON_THROW_ON_ERROR (PHP 7.3+) for exception-based handling:
<?php
// Traditional error checking
$data = json_decode('{"broken: true}');
if (json_last_error() !== JSON_ERROR_NONE) {
echo "Error: " . json_last_error_msg();
// Error: Syntax error
}
// Modern exception-based (PHP 7.3+)
try {
$data = json_decode('{"broken: true}', true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
echo "JSON error: " . $e->getMessage();
}
Nested Objects and Arrays
<?php
$response = json_decode('{
"users": [
{"id": 1, "name": "Alice", "roles": ["admin"]},
{"id": 2, "name": "Bob", "roles": ["viewer"]}
],
"meta": {"total": 2}
}', true);
// Access nested data
echo $response['users'][0]['roles'][0]; // admin
echo $response['meta']['total']; // 2
// Null coalescing for safe access
echo $response['users'][5]['name'] ?? 'not found'; // not found
Custom Types
<?php
class User {
public function __construct(
public readonly int $id,
public readonly string $name,
public readonly string $email
) {}
public static function fromJson(string $json): self {
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
return new self($data['id'], $data['name'], $data['email']);
}
public function toJson(): string {
return json_encode([
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
], JSON_THROW_ON_ERROR);
}
}
$user = User::fromJson('{"id": 1, "name": "Alice", "email": "a@b.com"}');
echo $user->name; // Alice
6. Ruby — JSON.parse
Ruby's standard library includes the json gem. Require it and use JSON.parse and JSON.generate.
Basic Parsing and Serialization
require 'json'
# Parse JSON string to Hash
data = JSON.parse('{"name": "Alice", "age": 30}')
puts data['name'] # Alice
# Serialize Hash to JSON string
json = JSON.generate({ name: 'Alice', age: 30 })
puts json # {"name":"Alice","age":30}
# Pretty-print
pretty = JSON.pretty_generate(data)
# Shorthand with to_json (available on Hash, Array, etc.)
puts({ name: 'Alice' }.to_json)
# Parse from file
data = JSON.parse(File.read('data.json'))
# Write to file
File.write('output.json', JSON.pretty_generate(data))
Error Handling
require 'json'
begin
data = JSON.parse('{"broken: true}')
rescue JSON::ParserError => e
puts "JSON parse error: #{e.message}"
end
Nested Objects and Arrays
require 'json'
response = JSON.parse('{
"users": [
{"id": 1, "name": "Alice", "roles": ["admin", "editor"]},
{"id": 2, "name": "Bob", "roles": ["viewer"]}
],
"meta": {"total": 2, "page": 1}
}')
puts response['users'][0]['roles'][1] # editor
puts response['meta']['total'] # 2
puts response.dig('users', 0, 'name') # Alice (safe nested access)
Symbolize Keys and Custom Types
require 'json'
# Symbolize keys for cleaner access
data = JSON.parse('{"name": "Alice"}', symbolize_names: true)
puts data[:name] # Alice
# Map to a custom class
class User
attr_reader :id, :name, :email
def initialize(id:, name:, email:)
@id = id
@name = name
@email = email
end
def self.from_json(json_string)
h = JSON.parse(json_string, symbolize_names: true)
new(**h)
end
def to_json(*args)
{ id: @id, name: @name, email: @email }.to_json(*args)
end
end
user = User.from_json('{"id": 1, "name": "Alice", "email": "a@b.com"}')
puts user.name # Alice
7. Rust — serde_json
Rust's ecosystem uses serde (serialization/deserialization framework) together with serde_json for JSON. Add both to your Cargo.toml:
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Basic Parsing and Serialization
use serde::{Deserialize, Serialize};
use serde_json;
#[derive(Debug, Serialize, Deserialize)]
struct User {
name: String,
age: u32,
#[serde(skip_serializing_if = "Option::is_none")]
email: Option<String>,
}
fn main() -> Result<(), serde_json::Error> {
// Parse JSON into a typed struct
let json = r#"{"name": "Alice", "age": 30}"#;
let user: User = serde_json::from_str(json)?;
println!("{}", user.name); // Alice
// Serialize struct to JSON
let output = serde_json::to_string(&user)?;
println!("{}", output);
// Pretty-print
let pretty = serde_json::to_string_pretty(&user)?;
println!("{}", pretty);
Ok(())
}
Error Handling
use serde_json;
fn parse_safely(input: &str) {
match serde_json::from_str::<serde_json::Value>(input) {
Ok(value) => println!("Parsed: {}", value),
Err(e) => {
eprintln!("Parse error at line {}, column {}: {}",
e.line(), e.column(), e);
}
}
}
// Specific struct parsing errors
let result: Result<User, _> = serde_json::from_str(r#"{"name": 42}"#);
match result {
Err(e) => eprintln!("Type mismatch: {}", e),
Ok(u) => println!("{:?}", u),
}
Dynamic JSON with serde_json::Value
use serde_json::{Value, json};
// Parse into a dynamic Value
let v: Value = serde_json::from_str(r#"{
"users": [{"name": "Alice"}, {"name": "Bob"}],
"count": 2
}"#)?;
// Access with indexing (returns Value::Null if missing)
println!("{}", v["users"][0]["name"]); // "Alice"
println!("{}", v["count"]); // 2
println!("{}", v["missing"]); // null
// Build JSON with the json! macro
let payload = json!({
"name": "Alice",
"scores": [95, 87, 92],
"active": true
});
Nested Structs and Enums
#[derive(Debug, Serialize, Deserialize)]
struct ApiResponse {
users: Vec<User>,
meta: Meta,
}
#[derive(Debug, Serialize, Deserialize)]
struct Meta {
total: usize,
page: usize,
}
// Tagged enums for polymorphic JSON
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type")]
enum Event {
#[serde(rename = "click")]
Click { x: i32, y: i32 },
#[serde(rename = "keypress")]
KeyPress { key: String },
}
// {"type": "click", "x": 100, "y": 200} -> Event::Click { x: 100, y: 200 }
8. C# — System.Text.Json
Since .NET Core 3.0, C# includes System.Text.Json as a high-performance, built-in JSON library. It is the recommended choice for new projects, replacing the older Newtonsoft.Json (Json.NET).
Basic Parsing and Serialization
using System.Text.Json;
// Define a model class
public record User(string Name, int Age, string? Email = null);
// Parse JSON into an object
string json = "{\"Name\": \"Alice\", \"Age\": 30}";
User? user = JsonSerializer.Deserialize<User>(json);
Console.WriteLine(user?.Name); // Alice
// Serialize to JSON
string output = JsonSerializer.Serialize(user);
Console.WriteLine(output);
// Pretty-print with options
var options = new JsonSerializerOptions { WriteIndented = true };
string pretty = JsonSerializer.Serialize(user, options);
// Case-insensitive property matching (common for web APIs)
var webOptions = new JsonSerializerOptions {
PropertyNameCaseInsensitive = true
};
User? u = JsonSerializer.Deserialize<User>(
"{\"name\": \"Alice\", \"age\": 30}", webOptions);
Error Handling
using System.Text.Json;
try {
var data = JsonSerializer.Deserialize<User>("not json");
} catch (JsonException ex) {
Console.Error.WriteLine($"JSON error: {ex.Message}");
Console.Error.WriteLine($"Path: {ex.Path}");
Console.Error.WriteLine($"Line: {ex.LineNumber}, Byte: {ex.BytePositionInLine}");
}
Nested Objects and Arrays
using System.Text.Json;
using System.Collections.Generic;
public record ApiResponse(
List<User> Users,
MetaInfo Meta
);
public record MetaInfo(int Total, int Page);
string json = @"{
""Users"": [
{""Name"": ""Alice"", ""Age"": 30},
{""Name"": ""Bob"", ""Age"": 25}
],
""Meta"": {""Total"": 2, ""Page"": 1}
}";
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var response = JsonSerializer.Deserialize<ApiResponse>(json, options);
Console.WriteLine(response?.Users[0].Name); // Alice
Console.WriteLine(response?.Meta.Total); // 2
Dynamic JSON with JsonDocument and JsonNode
using System.Text.Json;
using System.Text.Json.Nodes;
// JsonDocument (read-only, high performance)
using var doc = JsonDocument.Parse(json);
JsonElement root = doc.RootElement;
string name = root.GetProperty("Users")[0].GetProperty("Name").GetString()!;
// JsonNode (.NET 6+, mutable, more flexible)
JsonNode? node = JsonNode.Parse(json);
string? firstName = node?["Users"]?[0]?["Name"]?.GetValue<string>();
Console.WriteLine(firstName); // Alice
// Modify and re-serialize
node!["Users"]![0]!["Name"] = "Charlie";
Console.WriteLine(node.ToJsonString());
Custom Converters and Naming Policies
using System.Text.Json;
using System.Text.Json.Serialization;
// Use camelCase for JSON property names (matching JS conventions)
var options = new JsonSerializerOptions {
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
WriteIndented = true
};
// Custom attribute for property naming
public class Product {
[JsonPropertyName("product_name")]
public string Name { get; set; } = "";
[JsonIgnore]
public string InternalCode { get; set; } = "";
public decimal Price { get; set; }
}
Common JSON Pitfalls (All Languages)
Regardless of which language you use, certain JSON pitfalls trip up developers repeatedly. Here are the ones to watch for.
Trailing Commas
JSON does not allow trailing commas. This is valid JavaScript but invalid JSON:
// INVALID JSON - will fail in every parser
{
"name": "Alice",
"age": 30,
}
// VALID JSON
{
"name": "Alice",
"age": 30
}
This is the single most common JSON syntax error. It happens frequently when you add items to arrays or objects and forget to remove the last comma. Our JSON Validator will flag the exact line and column where a trailing comma appears.
Single Quotes
JSON requires double quotes for all strings and keys. Single quotes are not valid:
// INVALID - single quotes
{'name': 'Alice'}
// VALID - double quotes only
{"name": "Alice"}
Python developers are especially susceptible to this because Python's repr() uses single quotes. When logging dictionaries, always use json.dumps() rather than str().
NaN, Infinity, and Undefined
JSON has no representation for NaN, Infinity, -Infinity, or undefined. Different languages handle this differently:
// JavaScript: JSON.stringify silently converts or drops
JSON.stringify({ a: NaN, b: Infinity, c: undefined })
// Result: {"a":null,"b":null}
// Note: "c" is dropped entirely, NaN/Infinity become null
# Python: json.dumps raises ValueError by default
import json
json.dumps(float('nan')) # ValueError
json.dumps(float('nan'), allow_nan=True) # "NaN" (non-standard!)
// Go: encoding/json returns an error for NaN/Inf
// Use custom MarshalJSON to handle these cases
Date and Time Handling
JSON has no native date type. Dates are typically represented as ISO 8601 strings ("2026-02-11T10:30:00Z") or Unix timestamps (1771063800). Every language requires explicit conversion:
// JavaScript - Date objects serialize to ISO strings
JSON.stringify({ created: new Date() })
// {"created":"2026-02-11T10:30:00.000Z"}
// But parsing does NOT auto-convert back:
const obj = JSON.parse('{"created":"2026-02-11T10:30:00Z"}');
typeof obj.created // "string" (NOT a Date!)
// You need a reviver function (shown in the JavaScript section above)
# Python - datetime is not JSON serializable by default
import json
from datetime import datetime
json.dumps({"created": datetime.now()}) # TypeError!
# Solution: use .isoformat() or a default function
json.dumps({"created": datetime.now()}, default=str)
Large Numbers and Precision Loss
JavaScript uses 64-bit floating point (IEEE 754) for all numbers. Integers larger than Number.MAX_SAFE_INTEGER (2^53 - 1 = 9007199254740991) will lose precision:
// This is dangerous:
JSON.parse('{"id": 9007199254740993}')
// { id: 9007199254740992 } -- WRONG! Silently rounded!
// Solutions:
// 1. Use string IDs in your API
// 2. Use BigInt (requires custom reviver/replacer)
// 3. Use a BigInt-aware parser like json-bigint
This is a real-world problem. Twitter famously sends tweet IDs as both id (number) and id_str (string) specifically because of this issue.
Comments in JSON
Standard JSON does not support comments. If you need comments in configuration files, consider JSON5, JSONC (JSON with Comments, used by VS Code), YAML, or TOML:
// INVALID JSON - comments are not allowed
{
// This is a comment
"name": "Alice" /* inline comment */
}
// If you control the format, use JSONC or JSON5 instead
// VS Code's settings.json uses JSONC
Encoding Issues (Unicode and BOM)
JSON must be encoded in UTF-8 (RFC 8259). Watch out for:
- BOM (Byte Order Mark): Some editors add a UTF-8 BOM (
\xEF\xBB\xBF) at the start of the file. Many parsers reject this. - Escaped Unicode:
\u0041is valid in JSON and represents "A". But\x41is NOT valid JSON (it is valid JavaScript). - Unescaped control characters: Characters like tabs and newlines inside strings must be escaped (
\t,\n).
Performance Tips
For most applications, JSON parsing is not a bottleneck. But when you are processing millions of records, handling real-time data streams, or running on resource-constrained devices, performance matters. Here are practical tips across languages.
1. Use Streaming Parsers for Large Files
Loading an entire multi-gigabyte JSON file into memory at once is a recipe for OOM (Out of Memory) errors. Use streaming/incremental parsers instead:
# Python: ijson for streaming large files
import ijson
with open("huge_file.json", "rb") as f:
for item in ijson.items(f, "users.item"):
process(item) # Processes one user at a time
// Go: json.Decoder reads tokens incrementally
decoder := json.NewDecoder(reader)
for decoder.More() {
var item User
decoder.Decode(&item)
process(item)
}
// Java: Jackson's JsonParser for token-level streaming
JsonFactory factory = new JsonFactory();
JsonParser parser = factory.createParser(inputStream);
while (parser.nextToken() != null) {
// Process tokens incrementally
}
2. Reuse Parsers and Serializers
Creating new parser/serializer instances is expensive in some languages. Reuse them:
// Java: Reuse ObjectMapper (it is thread-safe)
// DO THIS:
private static final ObjectMapper MAPPER = new ObjectMapper();
// NOT THIS:
ObjectMapper mapper = new ObjectMapper(); // in every method call
// C#: Reuse JsonSerializerOptions
private static readonly JsonSerializerOptions Options = new() {
PropertyNameCaseInsensitive = true,
WriteIndented = true
};
// Pass Options to every Serialize/Deserialize call
// Go: json.Decoder/Encoder are cheap, but for hot paths consider
// pre-allocating buffers or using jsoniter/sonic
3. Parse Only What You Need
If you only need a few fields from a large JSON object, do not deserialize the entire thing:
// Go: Use json.RawMessage to defer parsing
type Envelope struct {
Type string `json:"type"`
Payload json.RawMessage `json:"payload"` // Not parsed yet
}
// Rust: #[serde(skip)] fields you don't need
#[derive(Deserialize)]
struct PartialUser {
name: String,
// Other fields are ignored automatically with default settings
}
// JavaScript: Destructure immediately after parsing
const { name, email } = JSON.parse(hugeJsonString);
// The entire object is still parsed, but you can GC the rest
# Python: Use JSONPath or jq for extraction
# Or parse into dict and access only needed keys
4. Consider Alternative Libraries
The standard library JSON parsers prioritize correctness and compatibility. If you need raw speed, consider these alternatives:
- JavaScript:
simdjson(WASM),fast-json-parsefor validation-only - Python:
orjson(3-10x faster than stdlib),ujson,msgspec - Go:
github.com/bytedance/sonic,github.com/json-iterator/go - Java: Jackson is already fast; for extreme cases,
dsl-json - Rust:
simd-json(uses SIMD instructions),serde_jsonis already very fast - C#:
System.Text.Jsonis faster than Newtonsoft; for more, consider source generators
5. Use Source Generation (C# and Rust)
Avoid runtime reflection by generating serialization code at compile time:
// C# (.NET 6+): Source generators eliminate reflection overhead
[JsonSerializable(typeof(User))]
[JsonSerializable(typeof(List<User>))]
internal partial class AppJsonContext : JsonSerializerContext {}
// Usage:
var user = JsonSerializer.Deserialize(json, AppJsonContext.Default.User);
// Rust: serde's derive macros already generate code at compile time
// This is the default behavior -- no extra setup needed
#[derive(Serialize, Deserialize)]
struct User { name: String, age: u32 }
6. Minimize Allocations
String allocations are a common bottleneck in JSON parsing. Tips to reduce them:
- Go: Use
json.Decoderwithio.Readerinstead ofjson.Unmarshalwith[]byteto avoid copying the entire input. - Rust: Use
serde_json::from_readerinstead offrom_strfor files. Use&strin structs with lifetime annotations to borrow from the input instead of allocating new strings. - C#: Use
Utf8JsonReaderfor zero-allocation parsing of UTF-8 byte spans. - Java: Use Jackson's streaming API (
JsonParser) for minimal object creation.
7. Benchmark Your Specific Workload
JSON parsing performance depends heavily on the shape of your data: deeply nested vs. flat, many small strings vs. few large ones, numeric-heavy vs. string-heavy. Always benchmark with your actual data rather than relying on generic benchmarks.
Quick Reference Table
| Language | Parse | Serialize | Error Type |
|---|---|---|---|
| JavaScript | JSON.parse(str) |
JSON.stringify(obj) |
SyntaxError |
| Python | json.loads(str) |
json.dumps(obj) |
JSONDecodeError |
| Go | json.Unmarshal() |
json.Marshal() |
*json.SyntaxError |
| Java (Jackson) | mapper.readValue() |
mapper.writeValueAsString() |
JsonProcessingException |
| Java (Gson) | gson.fromJson() |
gson.toJson() |
JsonSyntaxException |
| PHP | json_decode(str) |
json_encode(val) |
JsonException |
| Ruby | JSON.parse(str) |
JSON.generate(obj) |
JSON::ParserError |
| Rust | serde_json::from_str() |
serde_json::to_string() |
serde_json::Error |
| C# | JsonSerializer.Deserialize() |
JsonSerializer.Serialize() |
JsonException |
Conclusion
JSON parsing is one of those tasks that seems trivially easy until you hit edge cases: trailing commas from a code generator, NaN values from a math library, 64-bit integer IDs that lose precision, or date strings that need to become real date objects. Every language handles these differently, and knowing the specific API, error types, and pitfalls for your language will save you hours of debugging.
The key takeaways:
- Always handle parse errors. Never assume incoming JSON is valid. Use try/catch (or Go's error return) for every parse call on untrusted input.
- Use typed deserialization (Go structs, Rust serde derives, Java POJOs, C# records) rather than dynamic
map/dictaccess whenever possible. It catches schema mismatches at parse time instead of at access time. - Watch for the universal pitfalls: trailing commas, single quotes,
NaN/Infinity, comments, and large number precision loss. - For performance: reuse serializer instances, use streaming parsers for large data, consider faster alternative libraries, and always benchmark with your actual payloads.
- JSON Formatter — Format, minify, and fix JSON syntax
- JSON Validator — Validate JSON with detailed error messages
- JSON Viewer — Explore JSON as a collapsible tree
- JSON Path Finder — Click any value to get its access path
- JSON Schema Validator — Validate payloads against a schema