eastwest/json
A lightweight Laravel/PHP package that wraps JSON handling in a clean, fluent API, making it easy to encode, decode, inspect, and manipulate JSON data safely inside your applications.
eastwest/json is a single-class, static-method wrapper around json_encode() / json_decode() whose only job is to throw a catchable exception instead of returning false and forcing you to call json_last_error() / json_last_error_msg() afterwards.
Install
composer require eastwest/json
There is no service provider and no facade — this is a framework-agnostic PHP library. Nothing to register, nothing to publish, no config file. Composer autoload is the entire setup.
Where to look first
Open src/Json.php. The public surface is essentially two static entry points (Json::encode() and Json::decode()), plus the exception classes it throws. Read those two methods before reading anything else — the whole library is smaller than most Laravel service providers, and you can audit it in five minutes.
First use case
Replace the defensive json_last_error() dance at your application's input boundary: webhook handlers, queued job payloads, and third-party API responses. Before:
$data = json_decode($payload, true);
if (json_last_error() !== JSON_ERROR_NONE) {
Log::error('Bad payload', ['error' => json_last_error_msg()]);
return;
}
After:
use EastWest\Json\Json;
try {
$data = Json::decode($payload, true);
} catch (\Exception $e) {
// $e->getMessage() carries the JSON error description
throw new InvalidWebhookPayload($payload, previous: $e);
}
The win is not the line count — it's that the failure travels up the stack as an exception, so it lands in your handler/reporter instead of being silently swallowed into null.
Decode at the edge, pass typed data inward. Treat this as an anti-corruption layer. Decode in the controller, form request, or job's handle(), then hand arrays/DTOs to the rest of the app. Never let Json::decode() calls scatter through models or services — otherwise a malformed payload surfaces deep in your domain with no context about where it came from.
Custom validation rule for readable messages. Laravel's built-in json validation rule tells the user "must be a valid JSON string" with no detail. If you want the actual parser message in the 422 response:
class ValidJson implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
try {
Json::decode($value);
} catch (\Exception $e) {
$fail("The {$attribute} field is not valid JSON: {$e->getMessage()}");
}
}
}
Eloquent cast that fails loudly. Custom casts that return null on a bad column value hide corruption until the day someone reads the record. A cast that throws turns a silent data bug into a 500 with a stack trace and an alert:
class JsonColumn implements CastsAttributes
{
public function get($model, $key, $value, $attributes)
{
return $value === null ? null : Json::decode($value, true);
}
public function set($model, $key, $value, $attributes)
{
return [$key => Json::encode($value)];
}
}
Be deliberate here: throwing during hydration means one corrupt row poisons an entire ->get(). For user-editable columns, prefer decoding in a repository method so you can skip or repair bad rows.
HTTP client responses. Http::get(...)->json() returns null for both "empty body" and "invalid JSON", which are very different bugs. Wrap the raw body instead so you can distinguish them:
$response = Http::get($url)->throw();
$payload = $response->body() === '' ? [] : Json::decode($response->body(), true);
Queue jobs. Letting the decode exception propagate is usually correct — it fails the job once, triggers your failed() handler, and lands in Horizon's exception list with the payload attached. Add $job->fail($e) semantics rather than return, so it doesn't retry pointlessly on a payload that will never be valid.
Test assertions. The exception-based API is pleasant in tests: $this->expectException(...) around a fixture that should be rejected, rather than asserting false and separately asserting the error code.
This package is archived (last release 2018-06-14) and has zero dependents. That is the headline finding. It is not abandoned because it's bad — it's abandoned because PHP superseded it. Weigh adoption accordingly: you are adding a dependency for roughly forty lines of code you could copy into app/Support/Json.php and own forever.
PHP 7.3+ already solved this. json_decode($s, true, 512, JSON_THROW_ON_ERROR) gives you the identical ergonomics with zero dependencies and a native JsonException. On PHP 8.3+, json_validate() covers the "is this valid?" question without decoding at all. The only remaining reason to reach for this package is a codebase pinned below 7.3, which for a modern Laravel app should not exist.
Flag and depth support is the practical risk. The wrapper's signatures may not forward the full $flags / $depth parameter set you're used to. If you need JSON_BIGINT_AS_STRING, JSON_PRESERVE_ZERO_FRACTION, or a depth above the default 512, verify by reading the source before relying on it — otherwise you silently lose the ability to handle large integers or deeply nested structures.
Static methods are untestable seams. You cannot inject or mock a static call. If you adopt it, wrap it once in a small injectable JsonDecoder/JsonEncoder service and call that everywhere — it costs ten minutes and buys you the ability to swap the implementation out for native JSON_THROW_ON_ERROR later without touching call sites.
Watch the null/empty ambiguities. json_decode('null') is valid JSON and returns null; so does json_decode('') (with a syntax error). A wrapper that throws on the second but returns null on the first is correct, but your calling code must still distinguish them — check the raw string, not just the result.
Exception hierarchy matters for your handlers. Confirm whether encode and decode failures throw the same class. If they don't, catch the shared parent (if one exists) or explicitly catch both, or catch (\Exception) will quietly swallow unrelated errors in the same try block.
Don't catch and discard. The most common misuse is try { Json::decode($x) } catch (\Exception) { return []; } — that recreates exactly the silent-failure behaviour the package exists to eliminate. If a caller can genuinely proceed without the data, the exception should still be logged or reported first.
Encoding side has the same trap. Json::encode() on an object with a resource, invalid UTF-8, or NAN will throw rather than return false. That is usually what you want for outbound API responses (fail the request, not the client), but it does mean a single bad byte in a log context array can turn a working endpoint into a 500. Sanitize or JSON_PARTIAL_OUTPUT_ON_ERROR where appropriate.
Migration tip. Because the API mirrors the native functions, swapping back out later is mechanical: Json::decode($s, true) → json_decode($s, true, 512, JSON_THROW_ON_ERROR). Adopt it behind a thin wrapper and this becomes a one-file change.
How can I help you explore Laravel packages today?