seld/jsonlint
A PHP JSON parser and linter that validates JSON with clear error messages, supports parsing, duplicate-key handling, comments, associative arrays, and UTF-8 validation.
Build vs. buy on error UX. JSON-related support tickets ("upload failed," "config invalid") get a cheap, permanent fix at near-zero cost: MIT license, no transitive dependencies, no service or extension requirements. The buy decision is trivial; the real decision it unlocks is where to invest engineering time — on the product surface, not on a parser.
Feature: human-readable validation errors. Turns an opaque json_decode() === null into a line number, caret pointer, and expected-token hint. This directly supports onboarding and self-serve flows (config uploads, settings blobs, webhook debugging, lang/ file editing) where the end user is the one who can fix the input.
Product rule: duplicate-key policy. DETECT_KEY_CONFLICTS vs. ALLOW_DUPLICATE_KEYS vs. ALLOW_DUPLICATE_KEYS_TO_ARRAY is not an engineering choice — it's a policy choice with user-visible consequences. Silent last-write-wins (PHP's default) is a data-loss bug class; this library lets the PM force the decision into the open: reject loudly, collect into __duplicates__, or explicitly accept. Backed by a dedicated DuplicateKeyException with structured details (key, line) for pinpoint error copy.
Product rule: strict-vs-tolerant JSON. ALLOW_COMMENTS enables a documented deviation from RFC JSON (dev-editable config files, "commented settings"). Adopting it is a roadmap-level commitment that needs an owner, because it makes your config format not-quite-JSON for every downstream consumer.
Data-quality gate: UTF-8 correctness. VALIDATE_UTF8_ENCODING / standalone Utf8Validator catches mojibake at ingestion rather than at render time — relevant to i18n, multilingual names, and any user-generated content pipeline. Reusable outside JSON, so one investment covers several features.
Roadmap enabler: CI quality gate. vendor/bin/jsonlint with --quiet lints files, URLs, and multiple targets — a zero-application-code gate for composer.json, fixtures, and seed data. Sequence this first: it's the cheapest win and produces a measurable baseline (broken JSON caught pre-merge vs. post-deploy).
Decision against: this is not a performance feature. The library explicitly cannot match native json_decode(). The supported pattern is decode-first, re-parse-on-failure. A PM should record this as an architectural constraint in the spec, not leave it to code review, because the failure mode is silent latency/CPU cost, not an error.
Decision against: not a schema validator. It validates syntax, never shape. Any "validate this webhook/config against expectations" feature still needs a schema layer — don't let "we have jsonlint" defer that conversation.
Metrics this makes measurable: JSON-related support ticket volume, failed-upload retry rate, median time-to-diagnose for malformed payloads, and CI-caught JSON breakages. Without a baseline, you cannot justify or defend the integration.
Adopt when:
Look elsewhere when:
json_decode() and accept generic errors.justinrainbow/json-schema) or Laravel's own json validation rule plus explicit assertions. jsonlint answers "is this valid JSON," never "is this the right JSON."composer.json and your actual Laravel/PHP support matrix before committing to a support window — this is documentation lag, not a contract.Non-negotiable preconditions for adoption: a documented "decode-first, lint-on-failure" rule enforced by review or static analysis; a payload size ceiling; a sanitized error-message format; and a named owner for the duplicate-key and comments policies.
To executives — one paragraph: This is a small, free, MIT-licensed PHP utility (1.3k stars, no dependencies, no infrastructure, no vendor contract) that turns "your JSON is invalid" into "line 2, column 24, expected }." It is one of the lowest-cost, highest-certainty improvements we can make to any self-serve or integration surface, because the people hitting these errors are usually our own users and partners, and today they file a support ticket instead. It is already proven in production by Composer itself. The work is roughly a day of engineering plus a policy decision on duplicate keys; the return is fewer JSON-related tickets, faster self-resolution, and a CI gate that catches broken config before it ships. The one thing we must actively manage is scope: this library is a diagnostics tool, not a schema validator and not a fast parser, so we should use it deliberately on the failure path only.
To engineering — the ask: Composer-install, then wire it as a stateless service with a thin first-party adapter (singleton binding + config-driven flag set) so we have one place to enforce the rule. Sequence: (1) CI lint gate on JSON artifacts, (2) FormRequest / ingestion error paths get rich messages, (3) duplicate-key and comments policy encoded in the adapter. Hard constraint, documented and reviewable: json_decode() first, JsonParser only on failure — parse() must never sit on a hot request path, because the cost shows up as latency and CPU, not as an error. Treat all exception text as untrusted output: sanitize, cap length, keep detail in logs. Verify the actual PHP constraint in composer.json against our support matrix before we commit. Wrap it so a maintainer change upstream can't reach into our codebase.
The honest risk paragraph: One primary maintainer; low churn means few regressions but slow turnaround if a parser edge case hits us. A past release fixed error messages leaking input data, so user-facing surfaces need sanitization ownership. The README's PHP version claim is stale. None of these are blockers — all of them are cheap to mitigate — but they need a named owner before we ship rich errors to customers.
How can I help you explore Laravel packages today?