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, and Set have 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 supported

TypeScript: 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

LanguageEncode functionDecode functionEncode returnsError handlingStreaming
JavaScriptJSON.stringify()JSON.parse()stringthrows SyntaxErrorthird-party (oboe.js)
Pythonjson.dumps()json.loads()strraises JSONDecodeErrorthird-party (ijson)
Gojson.Marshal()json.Unmarshal()[]byte, errorreturns errorbuilt-in (Encoder/Decoder)
Javamapper.writeValueAsString()mapper.readValue()Stringthrows JsonProcessingExceptionbuilt-in (JsonParser/JsonGenerator)
PHPjson_encode()json_decode()string|falsethrows JsonException (flag)third-party (halaxa/json-machine)
RubyJSON.generate()JSON.parse()Stringraises JSON::ParserErrorthird-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