Skip to content

JSON Validator — JavaScript 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 JavaScript implementation — the same logic the interactive tool runs, in a shareable, citable form.

// json-validator - JavaScript polyglot showcase port
// Language: JavaScript (ES module)
// CosmoDev polyglot showcase port of the json-validator tool.
// Ported from src/lib/jsonValidate.ts (canonical TypeScript logic).
//
// This is display source - part of CosmoDev's polyglot tool pages (dev.cosmolabs.org).
//
// Pure JSON validation logic. No DOM, no I/O. Deterministic and never throws.
//
// The host's JSON engine embeds the error offset as "at position N" in its
// SyntaxError message (V8/Node, Chrome, Firefox). JavaScriptCore (bun, Safari)
// does not. Position recovery is therefore best-effort: when the offset is
// present it is converted to a 1-based line/column, otherwise both fall back
// to null.

/**
 * Extract the 0-based char offset from a SyntaxError message ("at position N").
 * Returns null when no offset is reported.
 */
export function extractOffset(message) {
  const match = /at position (\d+)/.exec(message);
  return match ? Number(match[1]) : null;
}

/** Coerce a caught value into a human-readable message. */
export function errorMessage(value) {
  return value instanceof Error ? value.message : String(value);
}

/**
 * Normalize an engine error message for display.
 * Strips JavaScriptCore's "JSON Parse error: " prefix (case-insensitive).
 */
export function normalizeError(message) {
  return message.replace(/^JSON Parse error:\s*/i, '');
}

/**
 * Convert a 0-based char offset into a 1-based { line, column }.
 * Counts "\n", a lone "\r", and "\r\n" each as a single line break.
 */
export function offsetToLineCol(text, offset) {
  let line = 1;
  let column = 1;
  const max = Math.min(offset, text.length);
  for (let i = 0; i < max; i++) {
    const ch = text[i];
    if (ch === '\n') {
      line++;
      column = 1;
    } else if (ch === '\r') {
      line++;
      column = 1;
      if (text[i + 1] === '\n') i++; // treat CRLF as a single break
    } else {
      column++;
    }
  }
  return { line, column };
}

/** Map a parse error message to { line, column } (or nulls) using the input text. */
export function locateError(text, message) {
  const offset = extractOffset(message);
  return offset === null ? { line: null, column: null } : offsetToLineCol(text, offset);
}

/**
 * Validate a JSON string. Never throws.
 *  - empty / whitespace-only / non-string → invalid, "Input is empty", no location
 *  - valid JSON                          → valid: true
 *  - invalid JSON                        → valid: false, cleaned error, and a
 *                                         1-based line/column when the engine
 *                                         reports one
 */
export function validateJson(text) {
  if (typeof text !== 'string' || text.trim().length === 0) {
    return { valid: false, error: 'Input is empty', line: null, column: null };
  }
  try {
    JSON.parse(text);
    return { valid: true, error: null, line: null, column: null };
  } catch (e) {
    const raw = errorMessage(e);
    return { valid: false, error: normalizeError(raw), ...locateError(text, raw) };
  }
}

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 →