jsonformattingvalidation

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

ErrorCauseFix
Unexpected token 'Single quotesReplace with double quotes
Unexpected token }Trailing commaRemove last comma before }
Unexpected end of JSON inputTruncated JSONCheck network response or file read
Circular structureObject references itselfUse JSON.stringify replacer to handle
undefined in outputundefined is not valid JSONReplace 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

FormatSizeHuman-ReadableCommentsBinary TypesBest For
JSONMediumYesNoNoAPIs, config
YAMLMediumVeryYesNoConfig, CI
MessagePackSmallNoNoYesRPC, caching
Protocol BuffersVery smallNoNoYesHigh-perf RPC
CBORSmallNoNoYesIoT, binary APIs

Tools