JSON Encode and Decode: JavaScript, Python, Go, Java
Last updated:
JSON encoding and decoding is the bread-and-butter of modern software. Whether you call it serialization, marshalling, or stringify — the concept is the same: convert structured data to a portable text format and back again. This guide covers the canonical approach in JavaScript, Python, Go, Java, PHP, and Ruby, with copy-paste examples for each.
Definitions
- Encoding
- Converting a native data structure (object, dict, map, struct) into a JSON string. Also called serialization or marshalling.
- Decoding
- Parsing a JSON string and producing a native data structure. Also called deserialization or unmarshalling.
- Serialization
- The broader process of converting data into a format suitable for storage or transmission. JSON serialization specifically produces a JSON string.
- Deserialization
- Reconstructing a data structure from a serialized format. JSON deserialization parses a JSON string back into objects, dicts, structs, or maps.
- Round-trip
- Encoding an object to JSON and decoding it back, expecting an equivalent object. Not all values survive losslessly — JavaScript
Date,undefined,NaN,Map, andSethave special behavior.
JavaScript: JSON.stringify and JSON.parse
JavaScript has built-in JSON support via the global JSON object. No imports needed. JSON.stringify() encodes; JSON.parse() decodes.
// ─── Encoding (object → JSON string) ───────────────────────────────────────
const user = { id: 1, name: 'Ada Lovelace', active: true }
const json = JSON.stringify(user)
// '{"id":1,"name":"Ada Lovelace","active":true}'
// Pretty-print with 2-space indent
const pretty = JSON.stringify(user, null, 2)
// {
// "id": 1,
// "name": "Ada Lovelace",
// "active": true
// }
// Replacer: omit specific keys
const filtered = JSON.stringify(user, ['name', 'active'])
// '{"name":"Ada Lovelace","active":true}'
// Replacer function: transform values
const masked = JSON.stringify(user, (key, value) => {
if (key === 'name') return '***'
return value
})
// '{"id":1,"name":"***","active":true}'
// ─── Decoding (JSON string → object) ────────────────────────────────────────
const parsed = JSON.parse('{"id":1,"name":"Ada Lovelace","active":true}')
// { id: 1, name: 'Ada Lovelace', active: true }
// Reviver: transform values during parse
const withDate = JSON.parse(
'{"createdAt":"2026-05-19T00:00:00.000Z"}',
(key, value) => (key === 'createdAt' ? new Date(value) : value)
)
// { createdAt: Date object }
// ─── Error handling ──────────────────────────────────────────────────────────
try {
JSON.parse('{bad json}')
} catch (e) {
console.error(e.message) // Unexpected token 'b', "{bad json}" is not valid JSON
}
// ─── Edge cases ──────────────────────────────────────────────────────────────
JSON.stringify(undefined) // undefined (not a string)
JSON.stringify({ a: undefined }) // '{}' — undefined values are dropped
JSON.stringify(NaN) // 'null'
JSON.stringify(Infinity) // 'null'
JSON.stringify(new Date()) // '"2026-05-19T00:00:00.000Z"' (ISO string)
JSON.stringify(new Map([['k','v']])) // '{}' — Map not natively supportedTypeScript: Typed Encode / Decode
interface User {
id: number
name: string
active: boolean
}
// Encoding is the same — TypeScript types don't affect stringify
const user: User = { id: 1, name: 'Ada Lovelace', active: true }
const json: string = JSON.stringify(user)
// Decoding: JSON.parse returns any — cast or validate
const decoded = JSON.parse(json) as User // unsafe cast
// Or validate at runtime with Zod:
// import { z } from 'zod'
// const UserSchema = z.object({ id: z.number(), name: z.string(), active: z.boolean() })
// const safe: User = UserSchema.parse(JSON.parse(json))Python: json.dumps and json.loads
Python's json module is in the standard library — no pip install needed. json.dumps() encodes to a string; json.dump() writes to a file. json.loads() decodes a string; json.load() reads from a file.
import json
# ─── Encoding (dict → JSON string) ──────────────────────────────────────────
user = {"id": 1, "name": "Ada Lovelace", "active": True}
json_str = json.dumps(user)
# '{"id": 1, "name": "Ada Lovelace", "active": true}'
# Note: Python True → JSON true, Python None → JSON null
# Pretty-print
pretty = json.dumps(user, indent=2)
# Sort keys alphabetically
sorted_json = json.dumps(user, sort_keys=True)
# Compact (no spaces)
compact = json.dumps(user, separators=(',', ':'))
# '{"id":1,"name":"Ada Lovelace","active":true}'
# Write to a file
with open('user.json', 'w') as f:
json.dump(user, f, indent=2)
# ─── Decoding (JSON string → dict) ──────────────────────────────────────────
parsed = json.loads('{"id": 1, "name": "Ada Lovelace", "active": true}')
# {'id': 1, 'name': 'Ada Lovelace', 'active': True}
# Read from a file
with open('user.json') as f:
data = json.load(f)
# ─── Error handling ──────────────────────────────────────────────────────────
try:
json.loads('{bad json}')
except json.JSONDecodeError as e:
print(e.msg) # Expecting property name enclosed in double quotes
print(e.pos) # character position of the error
print(e.lineno) # line number
# ─── Custom serialization ────────────────────────────────────────────────────
from datetime import datetime, date
class DateEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, (datetime, date)):
return obj.isoformat()
return super().default(obj)
data = {"created": datetime(2026, 5, 19)}
json.dumps(data, cls=DateEncoder)
# '{"created": "2026-05-19T00:00:00"}'
# ─── dataclasses ────────────────────────────────────────────────────────────
from dataclasses import dataclass, asdict
@dataclass
class User:
id: int
name: str
active: bool = True
u = User(id=1, name="Ada Lovelace")
json.dumps(asdict(u))
# '{"id": 1, "name": "Ada Lovelace", "active": true}'Go: json.Marshal and json.Unmarshal
Go's encoding/json package is part of the standard library. json.Marshal() encodes to []byte; json.Unmarshal() decodes from []byte. Struct field names are controlled by `json:"..."` tags.
package main
import (
"encoding/json"
"fmt"
"log"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Active bool `json:"active"`
Secret string `json:"-"` // always omitted
Age int `json:"age,omitempty"` // omitted if zero value
}
func main() {
// ─── Encoding (struct → JSON bytes) ─────────────────────────────────────
user := User{ID: 1, Name: "Ada Lovelace", Active: true, Secret: "hidden"}
b, err := json.Marshal(user)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(b))
// {"id":1,"name":"Ada Lovelace","active":true}
// Secret is omitted due to json:"-"; Age is omitted due to omitempty + zero value
// Pretty-print
pretty, _ := json.MarshalIndent(user, "", " ")
fmt.Println(string(pretty))
// ─── Decoding (JSON bytes → struct) ─────────────────────────────────────
data := []byte(`{"id":2,"name":"Grace Hopper","active":false}`)
var decoded User
if err := json.Unmarshal(data, &decoded); err != nil {
log.Fatal(err)
}
fmt.Println(decoded.Name) // Grace Hopper
// ─── Decode into map (schema unknown at compile time) ────────────────────
var m map[string]interface{}
json.Unmarshal(data, &m)
fmt.Println(m["name"]) // Grace Hopper (type: interface{})
// ─── Streaming encode/decode ─────────────────────────────────────────────
// Encode to io.Writer (e.g. http.ResponseWriter):
// json.NewEncoder(w).Encode(user)
// Decode from io.Reader (e.g. http.Request.Body):
// var u User
// json.NewDecoder(r.Body).Decode(&u)
}
// ─── Custom marshalling ──────────────────────────────────────────────────────
type Timestamp struct{ time.Time }
func (t Timestamp) MarshalJSON() ([]byte, error) {
return json.Marshal(t.Unix()) // encode as Unix seconds
}
func (t *Timestamp) UnmarshalJSON(data []byte) error {
var seconds int64
if err := json.Unmarshal(data, &seconds); err != nil {
return err
}
t.Time = time.Unix(seconds, 0)
return nil
}Java: Jackson ObjectMapper
Jackson is the de-facto standard JSON library for Java. Add jackson-databind to your project, then use ObjectMapper for encoding and decoding. Create one ObjectMapper instance and reuse it — it is thread-safe after configuration.
// pom.xml dependency:
// <dependency>
// <groupId>com.fasterxml.jackson.core</groupId>
// <artifactId>jackson-databind</artifactId>
// <version>2.17.0</version>
// </dependency>
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonIgnore;
public class User {
@JsonProperty("id")
private int id;
@JsonProperty("name")
private String name;
@JsonProperty("active")
private boolean active;
@JsonIgnore
private String secret; // never included in JSON
// Constructors, getters, setters omitted for brevity
}
public class JsonExample {
// Reuse ObjectMapper — it is thread-safe
private static final ObjectMapper mapper = new ObjectMapper()
.enable(SerializationFeature.INDENT_OUTPUT); // pretty-print
public static void main(String[] args) throws Exception {
// ─── Encoding (object → JSON string) ────────────────────────────────
User user = new User(1, "Ada Lovelace", true);
String json = mapper.writeValueAsString(user);
// {"id":1,"name":"Ada Lovelace","active":true}
// Write to file
mapper.writeValue(new File("user.json"), user);
// ─── Decoding (JSON string → object) ────────────────────────────────
User decoded = mapper.readValue(json, User.class);
System.out.println(decoded.getName()); // Ada Lovelace
// Read from file
User fromFile = mapper.readValue(new File("user.json"), User.class);
// ─── Generic types ───────────────────────────────────────────────────
String listJson = "[{"id":1,"name":"Ada"},{"id":2,"name":"Grace"}]";
List<User> users = mapper.readValue(listJson, new TypeReference<List<User>>() {});
Map<String, Object> map = mapper.readValue(json, new TypeReference<Map<String, Object>>() {});
// ─── Tree model (JsonNode) ───────────────────────────────────────────
JsonNode root = mapper.readTree(json);
String name = root.get("name").asText(); // "Ada Lovelace"
int id = root.get("id").asInt(); // 1
// ─── Error handling ──────────────────────────────────────────────────
try {
mapper.readValue("{bad json}", User.class);
} catch (JsonProcessingException e) {
System.err.println(e.getMessage());
}
}
}PHP: json_encode and json_decode
PHP has built-in JSON functions since PHP 5.2. json_encode() encodes; json_decode() decodes. PHP 7.3+ added JSON_THROW_ON_ERROR to avoid checking json_last_error() manually.
<?php
// ─── Encoding (array/object → JSON string) ──────────────────────────────────
$user = ['id' => 1, 'name' => 'Ada Lovelace', 'active' => true];
$json = json_encode($user);
// '{"id":1,"name":"Ada Lovelace","active":true}'
// Pretty-print
$pretty = json_encode($user, JSON_PRETTY_PRINT);
// Compact + Unicode unescaped (avoid \uXXXX for non-ASCII)
$compact = json_encode($user, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
// Throw on error instead of returning false (PHP 7.3+)
$safe = json_encode($user, JSON_THROW_ON_ERROR);
// ─── Decoding (JSON string → array or object) ────────────────────────────────
$json = '{"id":1,"name":"Ada Lovelace","active":true}';
// Decode as associative array (second argument true = assoc array)
$arr = json_decode($json, true);
echo $arr['name']; // Ada Lovelace
// Decode as object (default)
$obj = json_decode($json);
echo $obj->name; // Ada Lovelace
// ─── Error handling (PHP 8.0+) ───────────────────────────────────────────────
try {
$result = json_decode('{bad json}', true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
echo $e->getMessage(); // Syntax error
}
// Legacy error handling (PHP < 7.3)
$result = json_decode($json, true);
if (json_last_error() !== JSON_ERROR_NONE) {
echo json_last_error_msg();
}
// ─── Encoding objects with JsonSerializable ──────────────────────────────────
class User implements JsonSerializable {
public function __construct(
public int $id,
public string $name,
private string $secret // excluded from JSON
) {}
public function jsonSerialize(): mixed {
return ['id' => $this->id, 'name' => $this->name];
}
}
$user = new User(1, 'Ada', 'password123');
echo json_encode($user);
// '{"id":1,"name":"Ada"}'Ruby: JSON.generate and JSON.parse
Ruby's json gem ships with MRI Ruby. require 'json' to load it. JSON.generate() encodes; JSON.parse() decodes. Objects can implement to_json for custom encoding.
require 'json'
# ─── Encoding (hash/array → JSON string) ────────────────────────────────────
user = { id: 1, name: 'Ada Lovelace', active: true }
json = JSON.generate(user)
# '{"id":1,"name":"Ada Lovelace","active":true}'
# Shorthand — same result
json = user.to_json
# Pretty-print
pretty = JSON.pretty_generate(user)
# {
# "id": 1,
# "name": "Ada Lovelace",
# "active": true
# }
# ─── Decoding (JSON string → hash) ───────────────────────────────────────────
parsed = JSON.parse('{"id":1,"name":"Ada Lovelace","active":true}')
# {"id"=>1, "name"=>"Ada Lovelace", "active"=>true}
# Keys are strings by default
# Symbol keys
parsed_sym = JSON.parse(json, symbolize_names: true)
# {id: 1, name: "Ada Lovelace", active: true}
puts parsed_sym[:name] # Ada Lovelace
# ─── Error handling ───────────────────────────────────────────────────────────
begin
JSON.parse('{bad json}')
rescue JSON::ParserError => e
puts e.message # unexpected token at '{bad json}'
end
# ─── Custom serialization ─────────────────────────────────────────────────────
class User
attr_reader :id, :name
def initialize(id, name, secret)
@id = id
@name = name
@secret = secret # not exposed
end
def to_json(*args)
{ id: @id, name: @name }.to_json(*args)
end
end
u = User.new(1, 'Ada', 'hidden')
puts u.to_json
# '{"id":1,"name":"Ada"}'
# ─── Reading/writing files ────────────────────────────────────────────────────
File.write('user.json', JSON.pretty_generate(user))
data = JSON.parse(File.read('user.json'))Language Comparison Table
| Language | Encode function | Decode function | Encode returns | Error handling | Streaming |
|---|---|---|---|---|---|
| JavaScript | JSON.stringify() | JSON.parse() | string | throws SyntaxError | third-party (oboe.js) |
| Python | json.dumps() | json.loads() | str | raises JSONDecodeError | third-party (ijson) |
| Go | json.Marshal() | json.Unmarshal() | []byte, error | returns error | built-in (Encoder/Decoder) |
| Java | mapper.writeValueAsString() | mapper.readValue() | String | throws JsonProcessingException | built-in (JsonParser/JsonGenerator) |
| PHP | json_encode() | json_decode() | string|false | throws JsonException (flag) | third-party (halaxa/json-machine) |
| Ruby | JSON.generate() | JSON.parse() | String | raises JSON::ParserError | third-party (yajl-ruby) |
FAQ
What is the difference between JSON encoding and JSON decoding?
Encoding converts a native data structure to a JSON string. Decoding parses a JSON string back into a native data structure. In JavaScript: JSON.stringify() encodes, JSON.parse() decodes. In Python: json.dumps() encodes, json.loads() decodes. The terms serialization/deserialization and marshal/unmarshal are used interchangeably.
Does JSON support single-quoted strings?
No. The JSON specification (RFC 8259) requires double-quoted strings. {"name": "Ada"} is valid JSON; {"{'name': 'Ada'}"} is not. This is a common source of confusion because Python dict literals and JavaScript object literals both allow single quotes. Always use double quotes when producing JSON programmatically — the standard library functions handle this automatically.
Is JSON.stringify synchronous or asynchronous?
Both JSON.stringify() and JSON.parse() are synchronous and blocking. They run on the main JavaScript thread. For small-to-medium payloads this is perfectly fine. For very large documents (tens of MB), the synchronous call can block the event loop long enough to cause latency spikes in a Node.js server. In those cases, offload to a Worker thread or use a streaming parser.
What does json.dumps() return in Python?
json.dumps() returns a str. The "s" suffix stands for "string." To write JSON directly to a file object, use json.dump() (without the "s"). Similarly, json.loads() accepts a string and json.load() reads from a file. This s/no-s pattern mirrors Python's pickle module convention.
What does json.Marshal return in Go?
json.Marshal() returns ([]byte, error) — a byte slice and an error. To get a Go string, convert: string(b). For streaming output (e.g., writing to an HTTP response), use json.NewEncoder(w).Encode(v) — it writes directly to an io.Writer without allocating the entire JSON in memory.
How does Jackson ObjectMapper encode and decode JSON in Java?
mapper.writeValueAsString(obj) encodes an object to a JSON string. mapper.readValue(json, MyClass.class) decodes a string to an object. For generic types like List<User>, use TypeReference: mapper.readValue(json, new TypeReference<List<User>>(){}. ObjectMapper is thread-safe after configuration — create one instance and reuse it throughout your application.
What is a round-trip in JSON encoding?
A round-trip means encoding an object to JSON then decoding it back, expecting an equivalent object. Not all values survive losslessly in JavaScript: Date objects become ISO strings, undefined values are dropped, NaN and Infinity become null, and Map/Set become {}. If your application depends on these values, encode them to supported JSON types explicitly before stringifying.
How do I pretty-print JSON when encoding?
Every major language provides a pretty-print option: JavaScript: JSON.stringify(obj, null, 2). Python: json.dumps(obj, indent=2). Go: json.MarshalIndent(obj, "", " "). Java: mapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj). PHP: json_encode($data, JSON_PRETTY_PRINT). Ruby: JSON.pretty_generate(obj). In production APIs, use compact JSON (no indentation) to minimize payload size; use pretty-printing for debugging, logs, or human-readable config files.
Further reading and primary sources
- MDN JSON.parse — JSON.parse reference on MDN
- Python json module — Python json module documentation