Skip to content

JSON Validator — C source

Validate JSON and pinpoint errors with line and column numbers. Clear valid/invalid verdict plus the exact error location - runs entirely in your browser.

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

// json-validator — C port: hand-rolled recursive-descent JSON validator, ported from src/lib/jsonValidate.ts.
#include <ctype.h>
#include <stdio.h>
#include <string.h>

typedef struct {
    int valid;
    const char *error; /* NULL when valid */
    int line, column;  /* 0 = no position reported */
} Result;

/* 1-based line/column for a 0-based offset; "\n", lone "\r" and "\r\n" each
 * count as one break — the algorithm shared by every polyglot port. */
static void line_col(const char *text, long offset, int *line, int *column) {
    int l = 1, c = 1;
    for (long i = 0; i < offset && text[i]; i++) {
        if (text[i] == '\n') { l++; c = 1; }
        else if (text[i] == '\r') { l++; c = 1; if (text[i + 1] == '\n') i++; }
        else c++;
    }
    *line = l; *column = c;
}

static void skip_ws(const char **p) { while (**p==' '||**p=='\t'||**p=='\n'||**p=='\r') (*p)++; }
static int value(const char **p);

/* Strings: any escape pair passes; a raw control char (< 0x20) fails. */
static int parse_string(const char **p) {
    (*p)++; /* opening quote */
    while (**p != '"') {
        if (!**p) return 0; /* unterminated */
        if (**p == '\\') { if (!(*p)[1]) return 0; *p += 2; }
        else { if ((unsigned char)**p < 0x20) return 0; (*p)++; }
    }
    (*p)++;
    return 1;
}

/* RFC 8259 number: -?int(.frac)?([eE][+-]?exp)? */
static int parse_number(const char **p) {
    if (**p == '-') (*p)++;
    if (!isdigit((unsigned char)**p)) return 0;
    if (**p == '0') (*p)++; else while (isdigit((unsigned char)**p)) (*p)++;
    if (**p == '.') { (*p)++; if (!isdigit((unsigned char)**p)) return 0; while (isdigit((unsigned char)**p)) (*p)++; }
    if (**p == 'e' || **p == 'E') {
        (*p)++;
        if (**p == '+' || **p == '-') (*p)++;
        if (!isdigit((unsigned char)**p)) return 0;
        while (isdigit((unsigned char)**p)) (*p)++;
    }
    return 1;
}

static int literal(const char **p, const char *word) {
    size_t n = strlen(word);
    if (strncmp(*p, word, n) != 0) return 0;
    *p += n;
    return 1;
}

static int value(const char **p) {
    skip_ws(p);
    if (**p == '"') return parse_string(p);
    if (**p == '{') {
        (*p)++; skip_ws(p);
        if (**p == '}') { (*p)++; return 1; }
        for (;;) {
            skip_ws(p);
            if (**p != '"' || !parse_string(p)) return 0;
            skip_ws(p);
            if (**p != ':') return 0;
            (*p)++;
            if (!value(p)) return 0;
            skip_ws(p);
            if (**p == ',') { (*p)++; continue; }
            return **p == '}' ? ((*p)++, 1) : 0;
        }
    }
    if (**p == '[') {
        (*p)++; skip_ws(p);
        if (**p == ']') { (*p)++; return 1; }
        for (;;) {
            if (!value(p)) return 0;
            skip_ws(p);
            if (**p == ',') { (*p)++; continue; }
            return **p == ']' ? ((*p)++, 1) : 0;
        }
    }
    if (**p == 't') return literal(p, "true");
    if (**p == 'f') return literal(p, "false");
    if (**p == 'n') return literal(p, "null");
    return parse_number(p);
}

/* Validate NUL-terminated JSON text. Never crashes. Runnable: cc c.c && ./a.out */
Result validate_json(const char *text) {
    const char *p = text;
    skip_ws(&p);
    if (!*p) return (Result){0, "Input is empty", 0, 0};
    int ok = value(&p);
    if (ok) { skip_ws(&p); ok = !*p; } /* reject trailing characters */
    if (ok) return (Result){1, NULL, 0, 0};
    Result r = {0, "Unexpected token", 0, 0};
    line_col(text, p - text, &r.line, &r.column);
    return r;
}

int main(void) {
    const char *samples[] = {"{\"a\": [1, 2, {\"b\": null}]}", "{\n  \"a\": ,\n}"};
    for (int i = 0; i < (int)(sizeof samples / sizeof *samples); i++) {
        Result r = validate_json(samples[i]);
        if (r.valid) puts("valid");
        else printf("invalid: %s (line %d, column %d)\n", r.error, r.line, r.column);
    }
    return 0;
}

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 →