Skip to content

JSON Formatter — Zig source

Beautify, minify and validate JSON instantly. Paste, format, copy - all in your browser. Share a link to your exact input.

This is the Zig implementation — the same logic the interactive tool runs, in a shareable, citable form.

// json-formatter — parse, pretty-print (2-space indent), and minify.
//
// Language: Zig (Zig 0.13, standard library: std.json)
// Source:   CosmoDev polyglot showcase port of the `json-formatter` tool,
//           ported from src/tools/JsonFormatter.tsx (the canonical
//           TypeScript island).
// License:  display source — part of CosmoDev's polyglot tool pages.
//
// Unlike the Rust/C/C++/Java/Swift ports (hand-written parsers), Zig's
// standard library ships a full JSON engine, so this port delegates to it:
// std.json.Value.object is a StringArrayHashMap, which preserves source
// insertion order just like a JavaScript engine, and std.json.stringify
// emits either compact output (the default) or the indented shape (": "
// after keys, "[]" / "{}" for empty containers, matching the island's
// JSON.stringify(parsed, null, 2)).
//
// Parity caveats: Value holds integers as i64 — exact up to 2^63, wider
// than JS Number's 2^53 — and doubles via Zig's shortest-round-trip float
// printing, which can differ from ECMAScript's Number::toString near the
// exponential-notation thresholds. std.json.stringify offers fixed indent
// widths (1/2/3/4 spaces), so a custom `indent` count maps onto the
// nearest supported level, defaulting to 2 (the UI's value). Parse errors
// are reported as the error name plus the byte offset from
// std.json.Diagnostics.

const std = @import("std");

/// Selects the output shape.
pub const Mode = enum { beautify, minify };

/// Mirrors the TypeScript { output, error } shape. `error_message` is null
/// on success or empty input ("error" alone is a Zig keyword).
/// All returned slices are allocated from the caller's arena.
pub const FormatResult = struct {
    output: []const u8,
    error_message: ?[]const u8 = null,
};

/// Map the requested indent width onto std.json's fixed levels, defaulting
/// to 2 (the UI's value).
fn whitespaceFor(indent: u8) std.json.StringifyOptions.Whitespace {
    return switch (indent) {
        1 => .indent_1,
        3 => .indent_3,
        4 => .indent_4,
        else => .indent_2,
    };
}

fn outOfMemory() FormatResult {
    return .{ .output = "", .error_message = "out of memory" };
}

/// Parse and re-emit a JSON document the way the live island does.
/// `indent` is the number of spaces per nesting level for `.beautify`
/// (pass 2 to match the UI). Empty/whitespace-only input yields empty
/// output, no error. `arena` owns every allocation this function makes.
pub fn formatJson(arena: std.mem.Allocator, input: []const u8, mode: Mode, indent: u8) FormatResult {
    const trimmed = std.mem.trim(u8, input, " \t\r\n");
    if (trimmed.len == 0) return .{ .output = "" };

    var diagnostics = std.json.Diagnostics{};
    const parsed = std.json.parseFromSliceLeaky(std.json.Value, arena, trimmed, .{
        .diagnostics = &diagnostics,
    }) catch |err| {
        // The error name plus position — the feel of the island's e.message.
        const message = std.fmt.allocPrint(
            arena,
            "{s} at byte {d}",
            .{ @errorName(err), diagnostics.byte_offset },
        ) catch return outOfMemory();
        return .{ .output = "", .error_message = message };
    };

    var out = std.ArrayList(u8).init(arena);
    const writer = out.writer();
    switch (mode) {
        .minify => std.json.stringify(parsed, .{}, writer) catch return outOfMemory(),
        .beautify => std.json.stringify(parsed, .{ .whitespace = whitespaceFor(indent) }, writer) catch return outOfMemory(),
    }
    return .{ .output = out.items };
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    var arena_state = std.heap.ArenaAllocator.init(gpa.allocator());
    defer arena_state.deinit();
    const arena = arena_state.allocator();

    const sample = "{\"name\":\"CosmoDev\",\"tools\":[\"json\",\"base64\"],\"free\":true,\"nested\":{\"ok\":1}}";
    const result = formatJson(arena, sample, .beautify, 2);
    if (result.error_message) |message| {
        std.debug.print("error: {s}\n", .{message});
    } else {
        std.debug.print("{s}\n", .{result.output});
    }
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →