markup/json
Markup JSON is a lightweight PHP package for working with JSON as markup. It offers a simple way to parse or render JSON data into readable HTML output, making it useful for Laravel and PHP projects that need to display or transform JSON content.
markup/json is a thin object-oriented wrapper around json_encode() / json_decode() that throws exceptions instead of returning false/null and requiring you to inspect json_last_error(). It predates JSON_THROW_ON_ERROR (added in PHP 7.3), so it is primarily useful on PHP 5.6–7.2 codebases, or as a compatibility shim in code that must run on those versions.
Minimal setup:
composer require markup/json
The first thing to do is open composer.json and src/ in the repo — the public surface is small (one facade class, one exception type). Confirm the exact namespace and exception FQCN before wiring it into your app; the description advertises a "PHP 7.3 style" API, which in practice means something close to:
use Markup\Json\Json;
// Decode — throws if the input is not valid JSON
$data = Json::decode('{"id":1,"name":"Widget"}');
// Encode — throws if the value cannot be serialized
$body = Json::encode(['id' => 1, 'name' => 'Widget']);
The first end-to-end use case is replacing the classic error-checking dance:
// Before
$data = json_decode($input, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \RuntimeException(json_last_error_msg());
}
// After
$data = Json::decode($input, true); // throws on failure
Note that on any runtime PHP 7.3+ you should strongly consider using native json_decode($input, true, 512, JSON_THROW_ON_ERROR) instead and keeping this package out of the dependency tree entirely.
Boundary parsing (recommended use case). The natural place for this package is at the HTTP/JSON boundary — the controller or request handler — so that malformed input becomes an exception you can map to a 400 response, and the rest of the domain layer only ever deals with arrays/objects.
try {
$payload = Json::decode($request->getContent(), true);
} catch (\Exception $e) { // tighten to the package's exception type
return new Response('Invalid JSON', 400);
}
Middleware integration. In Laravel or Symfony, wrap request-body parsing in middleware so every route benefits without per-controller try/catch. Laravel: a small middleware that reads $request->getContent(), decodes, and attaches the array to the request attributes; Symfony: a kernel.request listener.
Response serialization. Use Json::encode() when building raw Response objects (e.g. new Response(Json::encode($data), 200, ['Content-Type' => 'application/json'])) so encoding failures (invalid UTF-8, NAN/INF floats, recursion depth) surface as exceptions instead of being silently written as an empty body — the classic json_encode silent failure this package fixes.
Drop-in migration. For a legacy codebase, mechanically replace json_encode → Json::encode and json_decode → Json::decode and let the resulting exceptions bubble into the same handler you use for other input errors. Because the API mirrors the native signatures, this substitution is usually safe.
Testing seam. Because it is a single static entry point, you can also use it as a single place to add logging/observability around JSON handling — decode failures get one stack trace with the offending payload attached.
JSON_INVALID_UTF8_* flag support. Audit src/ yourself before production use.JSON_THROW_ON_ERROR makes the package redundant. Keep the wrapper only if you are pinned to 7.2 or below.Markup\Json\JsonException — confirm the actual FQCN in the source) rather than \Exception or \Throwable. Catching broadly will swallow unrelated bugs and makes debugging harder.null ambiguity is gone — but confirm it. The whole point is that a decode of "null" (valid JSON producing null) no longer collides with "decode failed, returned null". Check that your code doesn't still assume a falsy return means error.JSON_BIGINT_AS_STRING.INF/NAN are the two most common json_encode throwers — sanitize floats (is_finite($v)) before encoding metrics/billing payloads.Json::decodeOrFail() helper (or a small JsonParser service) if you need structured logging, metric collection, or a consistent error shape. This also gives you a future migration seam to JSON_THROW_ON_ERROR without touching call sites.getMessage() and the raw input (truncated) — the package's messages are short, and the original payload is what you actually need to reproduce the failure.How can I help you explore Laravel packages today?