lcobucci/jwt · error · Lcobucci\JWT\Encoding\CannotEncodeContent

Error while encoding to JSON

Error message

Error while encoding to JSON

What it means

JoseEncoder::jsonEncode() calls json_encode with JSON_THROW_ON_ERROR (plus JSON_UNESCAPED_SLASHES and JSON_UNESCAPED_UNICODE). If encoding fails, JsonException is caught and rethrown as CannotEncodeContent::jsonIssues($exception) with the message 'Error while encoding to JSON'. Common causes are malformed UTF-8, resources/closures/inf/nan values, or exceeding recursion/depth limits.

Solutions

  1. Inspect the chained JsonException via $e->getPrevious() (it carries the precise json_encode error code, e.g. JSON_ERROR_UTF8) to identify the offending value.
  2. Sanitize input to valid UTF-8 (e.g. mb_convert_encoding($value, 'UTF-8', 'UTF-8') or remove invalid sequences) before encoding.
  3. Remove non-scalar claim values (resources, closures) and replace INF/NAN with finite values.
  4. Reduce nesting depth below json_encode's 512-level limit for deeply nested payloads.

Example fix

// before
$token = $builder->withClaim('data', $rawFromDb)->getToken(...); // CannotEncodeContent
// after
$data = mb_convert_encoding($rawFromDb, 'UTF-8', 'UTF-8');
$token = $builder->withClaim('data', $data)->getToken(...);
Defensive patterns

Strategy: try-catch

Validate before calling

// Cheap pre-checks for the common failure modes
function isJsonEncodableSafe(mixed $value): bool
{
    if (is_string($value) && !preg_match('//u', $value)) {
        return false; // invalid UTF-8
    }
    if (is_float($value) && (is_infinite($value) || is_nan($value))) {
        return false;
    }
    return is_scalar($value) || $value === null || is_array($value);
}

Try / catch

use Lcobucci\JWT\Encoding\CannotEncodeContent;

try {
    $json = $encoder->jsonEncode($data);
} catch (CannotEncodeContent $e) {
    $jsonError = $e->getPrevious(); // JsonException with the precise json_encode error
    // sanitize/reject $data based on $jsonError->getCode()
}

Prevention

When it happens

Trigger: Passing data to jsonEncode() that json_encode cannot serialize: strings with invalid UTF-8, INF/NAN floats, resources or closures, or structures deeper than 512 levels (depth limit).

Common situations: Injecting claims built from raw binary or invalid-encoding database content into a JWT; accidentally putting a resource (e.g. from fopen) in a claim; deeply nested arrays from decoded payloads; locale/encoding mismatches producing non-UTF-8 strings.

Understand the failure class

Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.

Related errors


AI-assisted analysis of lcobucci/jwt@375813049c (2026-09-14). Data as JSON: /api/errors/a5674de7595e691c. Report an issue: GitHub.

Appendix: source

Thrown at src/Encoding/JoseEncoder.php:28

use function json_decode;
use function json_encode;

use const JSON_THROW_ON_ERROR;
use const JSON_UNESCAPED_SLASHES;
use const JSON_UNESCAPED_UNICODE;

/**
 * A utilitarian class that encodes and decodes data according to JOSE specifications
 */
final readonly class JoseEncoder implements Encoder, Decoder
{
    public function jsonEncode(mixed $data): string
    {
        try {
            return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
        } catch (JsonException $exception) {
            throw CannotEncodeContent::jsonIssues($exception);
        }
    }

    public function jsonDecode(string $json): mixed
    {
        try {
            return json_decode(json: $json, associative: true, flags: JSON_THROW_ON_ERROR);
        } catch (JsonException $exception) {
            throw CannotDecodeContent::jsonIssues($exception);
        }
    }

    public function base64UrlEncode(string $data): string
    {
        return SodiumBase64Polyfill::bin2base64(
            $data,
            SodiumBase64Polyfill::SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING,
        );

View on GitHub (pinned to 375813049c)