getgrav/grav · error · RuntimeException

Encoding JSON failed: {json_last_error_msg}

Error message

Encoding JSON failed: {json_last_error_msg}

What it means

JsonFormatter::encode() calls @json_encode($data, $options) and throws when it returns false with json_last_error() set, appending json_last_error_msg(). Typical causes: malformed UTF-8 inside string data (JSON_ERROR_UTF8 / MALFORMED_UTF8), INF/NAN floats or resources that have no JSON representation, infinite recursion, and structures deeper than the configured depth (JSON_ERROR_DEPTH).

Source

Thrown at system/src/Grav/Framework/File/Formatter/JsonFormatter.php:150

     * Returns true if JSON objects will be converted into associative arrays.
     *
     * @return bool
     */
    public function getDecodeAssoc(): bool
    {
        return $this->getConfig('decode_assoc');
    }

    /**
     * {@inheritdoc}
     * @see FileFormatterInterface::encode()
     */
    public function encode($data): string
    {
        $encoded = @json_encode($data, $this->getEncodeOptions());

        if ($encoded === false && json_last_error() !== JSON_ERROR_NONE) {
            throw new RuntimeException('Encoding JSON failed: ' . json_last_error_msg());
        }

        return $encoded ?: '';
    }

    /**
     * {@inheritdoc}
     * @see FileFormatterInterface::decode()
     */
    public function decode($data)
    {
        $decoded = @json_decode($data, $this->getDecodeAssoc(), $this->getDecodeDepth(), $this->getDecodeOptions());

        if (null === $decoded && json_last_error() !== JSON_ERROR_NONE) {
            throw new RuntimeException('Decoding JSON failed: ' . json_last_error_msg());
        }

        return $decoded;

View on GitHub (pinned to 6040efed04)

Solutions

  1. Sanitize strings to UTF-8 before saving: a recursive walk applying mb_convert_encoding($s, 'UTF-8', 'UTF-8') drops invalid sequences.
  2. Tolerate bad bytes at encode time: set JSON_INVALID_UTF8_SUBSTITUTE (or IGNORE) in the formatter's encode_options config.
  3. Remove NAN/INF values and resources from the payload — json_encode cannot represent them.
  4. Raise the encode depth in the JSON formatter config if the nesting is legitimate.

Example fix

// before
$file->save($data); // Encoding JSON failed: Malformed UTF-8 characters...

// after
array_walk_recursive($data, function (&$v) {
    if (is_string($v)) { $v = mb_convert_encoding($v, 'UTF-8', 'UTF-8'); }
});
$file->save($data);
Defensive patterns

Strategy: validation

Validate before calling

// Ensure every string is valid UTF-8 before encoding
array_walk_recursive($data, function (&$v) {
    if (is_string($v)) { $v = mb_convert_encoding($v, 'UTF-8', 'UTF-8'); }
});

Type guard

function isUtf8Clean(mixed $value): bool
{
    if (is_string($value)) { return mb_check_encoding($value, 'UTF-8'); }
    if (is_array($value)) { foreach ($value as $v) { if (!isUtf8Clean($v)) return false; } }
    return true;
}

Try / catch

try {
    $file->save($data);
} catch (\RuntimeException $e) {
    if (str_contains($e->getMessage(), 'Encoding JSON failed')) {
        array_walk_recursive($data, fn(&$v) => is_string($v) && $v = mb_convert_encoding($v, 'UTF-8', 'UTF-8'));
        $file->save($data); // single sanitized retry
    }
}

Prevention

When it happens

Trigger: Encoding arrays containing binary or legacy-encoded strings (ISO-8859-1/Windows-1252 from old DBs, filenames, uploads); payloads with NAN/INF; depth beyond the limit (512 default); accidentally captured closures/resources (e.g. when var-exporting an object graph).

Common situations: Migrating latin1 legacy data into Grav; form uploads exposing raw bytes; deep nested page data; data fetched from external APIs with broken encoding; serialized objects containing resources.

Related errors


AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17). Data as JSON: /api/errors/8994ac37cd6022b4. Report an issue: GitHub.