Skip to content

JSON Repair — Zig source

Fix broken JSON - trailing commas, single quotes, unquoted keys, comments, Python constants, BOM and truncated documents - and get clean pretty-printed JSON plus a list of every repair applied. Runs entirely in your browser.

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

//! JSON repair pipeline — Zig port of the json-repair tool: the two scanner
//! passes (quote conversion + truncation recovery) of the 7-pass pipeline in
//! typescript.ts / go.go; regex-shaped passes live in the higher-level ports.
const std = @import("std");
const Allocator = std.mem.Allocator;
/// Single-quoted strings -> double-quoted: escape inner double quotes,
/// collapse \' to ', keep every other escape; double-quoted copy verbatim.
fn singleToDoubleQuotes(alloc: Allocator, text: []const u8) Allocator.Error![]u8 {
    var out = std.ArrayList(u8).init(alloc);
    errdefer out.deinit();
    var i: usize = 0;
    while (i < text.len) {
        if (text[i] == '"') { // copy a double-quoted string verbatim
            try out.append('"');
            i += 1;
            while (i < text.len and text[i] != '"') {
                if (text[i] == '\\' and i + 1 < text.len) { try out.append(text[i]); i += 1; }
                try out.append(text[i]);
                i += 1;
            }
            if (i < text.len) { try out.append('"'); i += 1; }
        } else if (text[i] == '\'') {
            try out.append('"');
            i += 1;
            while (i < text.len and text[i] != '\'') {
                if (text[i] == '\\' and i + 1 < text.len) {
                    if (text[i + 1] == '\'') try out.append('\'') // \' -> '
                    else try out.appendSlice(text[i .. i + 2]); // other escapes kept
                    i += 2;
                } else if (text[i] == '"') {
                    try out.appendSlice("\\\"");
                    i += 1;
                } else { try out.append(text[i]); i += 1; }
            }
            if (i < text.len) i += 1; // skip the closing quote
            try out.append('"');
        } else { try out.append(text[i]); i += 1; }
    }
    return out.toOwnedSlice();
}
/// Recover truncated JSON: close an unterminated string, drop a dangling
/// comma, give a dangling colon a null value, then close open brackets.
fn closeTruncated(alloc: Allocator, text: []const u8) Allocator.Error![]u8 {
    const stack = try alloc.alloc(u8, text.len + 1);
    defer alloc.free(stack);
    var top: usize = 0;
    var in_string = false;
    var i: usize = 0;
    while (i < text.len) : (i += 1) {
        const c = text[i];
        if (in_string) {
            if (c == '\\' and i + 1 < text.len) i += 1 else if (c == '"') in_string = false;
        } else {
            if (c == '"') in_string = true;
            if (c == '{' or c == '[') { stack[top] = c; top += 1; }
            if (c == '}' or c == ']') { if (top > 0) top -= 1; }
        }
    }
    var out = std.ArrayList(u8).init(alloc);
    errdefer out.deinit();
    try out.appendSlice(text[0..i]);
    if (in_string) try out.append('"');
    while (out.items.len > 0 and (out.items[out.items.len - 1] == ',' or std.ascii.isWhitespace(out.items[out.items.len - 1]))) out.items.len -= 1;
    if (out.items.len > 0 and out.items[out.items.len - 1] == ':') try out.appendSlice(" null");
    while (top > 0) { top -= 1; try out.append(if (stack[top] == '{') '}' else ']'); }
    return out.toOwnedSlice();
}
/// The canonical repair() runs all 7 passes and pretty-prints; this excerpt
/// chains the two scanner passes (the arena frees the intermediates).
pub fn main() !void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();
    const alloc = arena.allocator();
    // {"name": "Ada", tags: ["x", "y"]}
    const quoted = try singleToDoubleQuotes(alloc, "{'name': 'Ada', tags: ['x', 'y',");
    const repaired = try closeTruncated(alloc, quoted);
    std.debug.print("{s}\n", .{repaired});
}

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 →