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 →