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
- Sanitize strings to UTF-8 before saving: a recursive walk applying mb_convert_encoding($s, 'UTF-8', 'UTF-8') drops invalid sequences.
- Tolerate bad bytes at encode time: set JSON_INVALID_UTF8_SUBSTITUTE (or IGNORE) in the formatter's encode_options config.
- Remove NAN/INF values and resources from the payload — json_encode cannot represent them.
- 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
- Normalize all input to UTF-8 at system boundaries (forms, APIs, uploads).
- Use utf8mb4 for MySQL connections so retrieved strings are clean.
- Configure encode options (depth, invalid-UTF8 handling) to match your data.
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
- Decoding markdown failed
- Failed to save file '%s': %s
- Decoding JSON failed: {json_last_error_msg}
- Encoding markdown failed
- Decoding serialized data failed
AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17).
Data as JSON: /api/errors/8994ac37cd6022b4.
Report an issue: GitHub.