Skip to content

Write a pretty-printed JSON file snippet

Serialize an in-memory value to JSON with indentation a human can read — the right default for config files, fixtures, and anything headed for a diff or a code review.

Serialize an in-memory value to JSON with indentation a human can read — the right default for config files, fixtures, and anything headed for a diff or a code review. The mechanics are one call everywhere; the traps are cosmetic but real: PHP and Swift escape forward slashes by default, Python escapes every non-ASCII codepoint unless ensure_ascii=False, Go and Rust omit the trailing newline POSIX tools expect, and the indent is fixed in several stacks (two spaces in serde_json and System.Text.Json, four in PHP and kotlinx.serialization). SQL is omitted: the engine cannot write a file on the developer's machine — COPY ... TO only lands on the server and needs superuser.

Runnable recipe · 14 languages
Files & Datajsonfilesserialization

Every language

14 implementations, copy-ready. One at a time with syntax highlighting, or all inline.

JSJavaScript
import { writeFile } from 'node:fs/promises';

const project = { name: 'cosmodev', stars: 42 };
await writeFile('out.json', JSON.stringify(project, null, 2) + '\n', 'utf8');

null is the replacer slot; the indent takes a number or a string like '\t'. JSON.stringify never appends a trailing newline.

TSTypeScript
import { writeFile } from 'node:fs/promises';

interface Project {
  name: string;
  stars: number;
}

const project: Project = { name: 'cosmodev', stars: 42 };
await writeFile('out.json', JSON.stringify(project, null, 2) + '\n', 'utf8');

Serialization walks own enumerable properties only — class instances lose methods, and undefined-valued fields vanish from the output.

GoGo
package main

import (
	"encoding/json"
	"os"
)

func main() {
	data := map[string]any{"name": "cosmodev", "stars": 42}
	out, err := json.MarshalIndent(data, "", "  ")
	if err != nil {
		panic(err)
	}
	if err := os.WriteFile("out.json", append(out, '\n'), 0o644); err != nil {
		panic(err)
	}
}

MarshalIndent takes a prefix then the indent string. Map keys come out sorted; Go emits no trailing newline, hence the append.

RsRust
use std::fs;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let project = serde_json::json!({
        "name": "cosmodev",
        "stars": 42,
    });
    fs::write("out.json", serde_json::to_string_pretty(&project)? + "\n")?;
    Ok(())
}

serde_json = "1" in Cargo.toml; the json! macro builds a Value without a serde derive. to_string_pretty hardcodes two-space indent — implement serde_json::ser::Formatter for anything else.

PHPPHP
<?php
$project = ['name' => 'cosmodev', 'stars' => 42];
$flags = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
file_put_contents('out.json', json_encode($project, $flags) . PHP_EOL);

JSON_PRETTY_PRINT indents with four spaces. Without JSON_UNESCAPED_SLASHES every '/' becomes '\/', and without JSON_UNESCAPED_UNICODE every non-ASCII character becomes \uXXXX.

PyPython
import json

project = {"name": "cosmodev", "stars": 42}
with open("out.json", "w", encoding="utf-8") as f:
    json.dump(project, f, indent=2, ensure_ascii=False)
    f.write("\n")

json.dump writes straight to the file object. indent also fixes the separators; ensure_ascii=False is what keeps UTF-8 readable instead of \uXXXX escapes.

CC
#include <jansson.h>

int main(void) {
    json_t *root = json_pack("{s:s, s:i}", "name", "cosmodev", "stars", 42);
    if (!root) {
        return 1;
    }
    if (json_dump_file(root, "out.json", JSON_INDENT(2)) != 0) {
        json_decref(root);
        return 1;
    }
    json_decref(root);
    return 0;
}

Link with -ljansson. Standard C has no JSON support — jansson is the small, canonical choice; JSON_INDENT(n) sets the indent width and json_dump_file returns 0 on success.

C++C++
#include <fstream>
#include <nlohmann/json.hpp>

int main() {
    nlohmann::json project = {
        {"name", "cosmodev"},
        {"stars", 42},
    };
    std::ofstream out("out.json");
    out << project.dump(2) << '\n';
}

nlohmann/json is header-only — drop the single include in or fetch it via a package manager. dump() with no argument is compact; dump(2) is the two-space pretty form.

C#C#
using System.Text.Json;

var options = new JsonSerializerOptions { WriteIndented = true };
var project = new { name = "cosmodev", stars = 42 };
File.WriteAllText("out.json", JsonSerializer.Serialize(project, options) + "\n");

System.Text.Json ships with .NET. WriteIndented is hardcoded to two spaces; cache and reuse the options instance — each one builds and holds its own metadata cache.

JvJava
import com.google.gson.GsonBuilder;

import java.nio.file.Files;
import java.nio.file.Path;

record Project(String name, int stars) {}

public class WritePrettyJson {
    public static void main(String[] args) throws Exception {
        var gson = new GsonBuilder().setPrettyPrinting().create();
        Files.writeString(Path.of("out.json"), gson.toJson(new Project("cosmodev", 42)) + "\n");
    }
}

Dependency com.google.code.gson:gson; setPrettyPrinting() is two-space. Jackson's equivalent is mapper.writerWithDefaultPrettyPrinter().

SwSwift
import Foundation

struct Project: Codable {
    let name: String
    let stars: Int
}

let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys, .withoutEscapingSlashes]
let data = try encoder.encode(Project(name: "cosmodev", stars: 42))
try String(decoding: data, as: UTF8.self)
    .write(toFile: "out.json", atomically: true, encoding: .utf8)

.prettyPrinted is two-space. Without .withoutEscapingSlashes (macOS 10.15 / iOS 13+) every '/' in the output is escaped as '\/'.

KtKotlin
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.io.File

@kotlinx.serialization.Serializable
data class Project(val name: String, val stars: Int)

fun main() {
    val json = Json { prettyPrint = true; prettyPrintIndent = "  " }
    File("out.json").writeText(json.encodeToString(Project("cosmodev", 42)))
}

Needs kotlinx-serialization-json plus the kotlinx.serialization compiler plugin. prettyPrintIndent defaults to four spaces and only accepts whitespace strings.

RbRuby
require 'json'

project = { 'name' => 'cosmodev', 'stars' => 42 }
File.write('out.json', JSON.pretty_generate(project) + "\n")

JSON is in the stdlib. pretty_generate is the two-space twin of JSON.generate — the compact form — and string keys stay strings.

ZigZig
const std = @import("std");

pub fn main() !void {
    const data = .{
        .name = "cosmodev",
        .stars = 42,
        .tags = [_][]const u8{ "json", "files" },
    };
    const file = try std.fs.cwd().createFile("out.json", .{});
    defer file.close();
    var buf = std.io.bufferedWriter(file.writer());
    try std.json.stringify(data, .{ .whitespace = .indent_2 }, buf.writer());
    try buf.flush();
}

std.json is built in. stringify serializes plain struct literals via comptime reflection; .whitespace = .indent_2 picks the indent width.

Keep going

Read the data-formats cheatsheet →