JSON Validator — PHP 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 PHP implementation — the same logic the interactive tool runs, in a shareable, citable form.
<?php
// json-validator — PHP polyglot showcase port
// Language: PHP
// 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. Uses ext-json (always bundled with PHP since 8.0).
// PHP's json_decode does not report an error offset in JsonException::getMessage,
// so position recovery gracefully returns null line/column — the same fallback
// the TypeScript port uses for engines that omit "at position N". The
// offset_to_line_col helper is still provided for parity and for foreign engines.
declare(strict_types=1);
namespace CosmoDev\JsonValidator;
/**
* Extract the 0-based offset from an engine message of the form "at position N".
* PHP's own error messages never include this marker, so this returns null for
* PHP-produced errors; kept for parity with the other ports.
*/
function extract_offset(string $message): ?int
{
if (preg_match('/at position (\d+)/', $message, $m) === 1) {
return (int) $m[1];
}
return null;
}
/** Coerce a caught value into a human-readable message. */
function error_message(mixed $value): string
{
if ($value instanceof \Throwable) {
return $value->getMessage();
}
// Non-throwable: stringify only what is sensibly stringifiable.
return is_scalar($value) ? (string) $value : '';
}
/**
* Normalize an engine error message for display.
* Strips JavaScriptCore's "JSON Parse error: " prefix (case-insensitive).
*/
function normalize_error(string $message): string
{
return preg_replace('/^JSON Parse error:\s*/i', '', $message);
}
/**
* Convert a 0-based byte offset into a 1-based [line, column].
* Counts "\n", a lone "\r", and "\r\n" each as one line break. Columns are
* measured in multibyte characters, so a UTF-8 rune counts once.
*/
function offset_to_line_col(string $text, int $offset): array
{
$line = 1;
$column = 1;
$len = min($offset, strlen($text));
for ($i = 0; $i < $len; $i++) {
$byte = $text[$i];
// Skip UTF-8 continuation bytes so multibyte runes count as one column.
if ((ord($byte) & 0xC0) === 0x80) {
continue;
}
if ($byte === "\n") {
$line++;
$column = 1;
} elseif ($byte === "\r") {
$line++;
$column = 1;
if (($i + 1) < $len && $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. */
function locate_error(string $text, string $message): array
{
$offset = extract_offset($message);
if ($offset === null) {
return ['line' => null, 'column' => null];
}
[$line, $column] = offset_to_line_col($text, $offset);
return ['line' => $line, 'column' => $column];
}
/**
* Validate a JSON string. Never throws.
* - empty / whitespace-only / non-string → invalid, "Input is empty"
* - valid JSON → valid: true
* - invalid JSON → valid: false, cleaned message,
* null line/column (PHP reports no offset)
*/
function validate_json(mixed $text): array
{
if (!is_string($text) || trim($text) === '') {
return ['valid' => false, 'error' => 'Input is empty', 'line' => null, 'column' => null];
}
try {
// depth 512 mirrors PHP's documented default stack depth. The decoded
// value is discarded — we only care about well-formedness.
json_decode($text, true, 512, JSON_THROW_ON_ERROR);
return ['valid' => true, 'error' => null, 'line' => null, 'column' => null];
} catch (\JsonException $e) {
$raw = error_message($e);
return array_merge(
['valid' => false, 'error' => normalize_error($raw)],
locate_error($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 →