JSON Formatting and Validation Guide
· Cosyslabs
JSON (JavaScript Object Notation) is the standard data interchange format for web APIs and configuration files. It represents structured data as human-readable text using six data types: strings, numbers, booleans, null, arrays, and objects. Valid JSON is strict about syntax — a single misplaced comma or unquoted key causes a parse failure.
JSON Syntax Rules
JSON has exactly six value types:
{
"string": "text in double quotes only",
"number": 42,
"float": 3.14,
"boolean": true,
"null_value": null,
"array": [1, "two", true, null],
"object": {
"nested": "value"
}
}
Strict Rules (Common Mistakes)
Strings must use double quotes — not single quotes:
// INVALID
{ 'key': 'value' }
// VALID
{ "key": "value" }
No trailing commas:
// INVALID
{
"a": 1,
"b": 2, ← trailing comma
}
// VALID
{
"a": 1,
"b": 2
}
No comments:
// This is NOT valid JSON
{
// "debug": true ← JSON has no comments
"production": true
}
Keys must be strings:
// INVALID
{ 42: "value" }
// VALID
{ "42": "value" }
Special values use lowercase:
{ "active": true, "deleted": false, "data": null }
// NOT: True, False, None, NULL, undefined
Formatting: Compact vs Pretty-Printed
JSON can be compact (one line, minimal whitespace) or pretty-printed (indented for readability):
// Compact — smaller payload for API responses
{"user":{"id":1,"name":"Alice","active":true}}
// Pretty-printed — for debugging, config files, logs
{
"user": {
"id": 1,
"name": "Alice",
"active": true
}
}
In code:
// Compact
JSON.stringify(data);
// Pretty-printed (2-space indent)
JSON.stringify(data, null, 2);
// Pretty-printed (tab indent)
JSON.stringify(data, null, "\t");
import json
# Compact
json.dumps(data)
# Pretty-printed
json.dumps(data, indent=2, sort_keys=True)
Parsing JSON
JavaScript
// Parse JSON string to object
const obj = JSON.parse('{"name": "Alice", "age": 30}');
// Always handle errors
try {
const data = JSON.parse(untrustedInput);
} catch (e) {
console.error("Invalid JSON:", e.message);
}
// Reviver function for custom transformations
const data = JSON.parse(jsonString, (key, value) => {
// Convert date strings to Date objects
if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}/.test(value)) {
return new Date(value);
}
return value;
});
Python
import json
# Parse string
data = json.loads('{"name": "Alice", "age": 30}')
# Parse file
with open("data.json") as f:
data = json.load(f)
# Custom decoder for dates
from datetime import datetime
def decode_datetime(obj):
if "date" in obj:
obj["date"] = datetime.fromisoformat(obj["date"])
return obj
data = json.loads(json_string, object_hook=decode_datetime)
TypeScript (with type safety)
interface User {
id: number;
name: string;
email: string;
}
// Type assertion (no runtime validation)
const user = JSON.parse(jsonString) as User;
// Runtime validation with Zod
import { z } from "zod";
const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
});
const result = UserSchema.safeParse(JSON.parse(jsonString));
if (result.success) {
const user = result.data; // typed as User
}
Go
import "encoding/json"
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
// Unmarshal
var user User
err := json.Unmarshal([]byte(jsonString), &user)
// Marshal
jsonBytes, err := json.Marshal(user)
jsonPretty, err := json.MarshalIndent(user, "", " ")
// Stream decode (large files)
decoder := json.NewDecoder(reader)
for decoder.More() {
var item Item
if err := decoder.Decode(&item); err != nil {
break
}
process(item)
}
JSON Schema Validation
JSON Schema (draft 2020-12) is the standard for validating JSON structure:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["id", "name", "email"],
"properties": {
"id": {
"type": "integer",
"minimum": 1
},
"name": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"email": {
"type": "string",
"format": "email"
},
"age": {
"type": "integer",
"minimum": 0,
"maximum": 150
},
"tags": {
"type": "array",
"items": { "type": "string" },
"uniqueItems": true
}
},
"additionalProperties": false
}
Validating in JavaScript:
import Ajv from "ajv";
import addFormats from "ajv-formats";
const ajv = new Ajv();
addFormats(ajv);
const validate = ajv.compile(schema);
const valid = validate(data);
if (!valid) {
console.error(validate.errors);
}
Common JSON Errors and Fixes
| Error | Cause | Fix |
|---|---|---|
Unexpected token ' | Single quotes | Replace with double quotes |
Unexpected token } | Trailing comma | Remove last comma before } |
Unexpected end of JSON input | Truncated JSON | Check network response or file read |
Circular structure | Object references itself | Use JSON.stringify replacer to handle |
undefined in output | undefined is not valid JSON | Replace with null or omit the key |
Handling Circular References
// Error: circular structure to JSON
const obj = { name: "test" };
obj.self = obj; // circular!
JSON.stringify(obj); // throws!
// Fix with replacer
function safeStringify(obj) {
const seen = new WeakSet();
return JSON.stringify(obj, (key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) return "[Circular]";
seen.add(value);
}
return value;
});
}
BigInt Values
// JSON.stringify throws for BigInt
JSON.stringify({ id: 9007199254740993n }); // TypeError
// Serialize BigInt as string
JSON.stringify({ id: 9007199254740993n }, (key, value) =>
typeof value === "bigint" ? value.toString() : value
);
// '{"id":"9007199254740993"}'
JSON at Scale
Streaming Large Files
// Node.js — stream parse a multi-GB JSON array
import { createReadStream } from "fs";
import { chain } from "stream-chain";
import { parser } from "stream-json";
import { streamArray } from "stream-json/streamers/StreamArray.js";
const pipeline = chain([
createReadStream("large-file.json"),
parser(),
streamArray(),
]);
for await (const { value } of pipeline) {
await processItem(value);
}
JSON Lines (NDJSON)
For large datasets, JSON Lines (one JSON object per line) is more streaming-friendly than a single large array:
{"id":1,"name":"Alice"}
{"id":2,"name":"Bob"}
{"id":3,"name":"Carol"}
// Parse NDJSON
const lines = ndjsonString.split("\n").filter(Boolean);
const records = lines.map(line => JSON.parse(line));
JSON vs Alternatives
| Format | Size | Human-Readable | Comments | Binary Types | Best For |
|---|---|---|---|---|---|
| JSON | Medium | Yes | No | No | APIs, config |
| YAML | Medium | Very | Yes | No | Config, CI |
| MessagePack | Small | No | No | Yes | RPC, caching |
| Protocol Buffers | Very small | No | No | Yes | High-perf RPC |
| CBOR | Small | No | No | Yes | IoT, binary APIs |
Tools
- JSON Formatter — format, validate, and minify JSON
- JSON to YAML — convert between formats
- JSON Diff — compare two JSON documents
- YAML Formatter — format and validate YAML