twigphp/Twig · error · Twig\Error\RuntimeError

The " " attribute value cannot be JSON encoded.

Error message

The "%s" attribute value cannot be JSON encoded.

What it means

html_attr_value() JSON-encodes non-scalar, non-Stringable, non-null attribute values so they can be rendered. If json_encode() with JSON_THROW_ON_ERROR fails (e.g. malformed UTF-8, recursion, unsupported resources), the JsonException is wrapped in this RuntimeError with the attribute name for context.

Solutions

  1. Fix the underlying data: sanitize invalid UTF-8 (e.g. mb_convert_encoding($s, 'UTF-8', 'UTF-8')).
  2. Implement Stringable on the value object so it is used verbatim instead of JSON-encoded.
  3. Convert the value to a scalar/array of scalars before passing.
  4. Inspect $e->getPrevious() (the JsonException) to see the exact encoding failure.

Example fix

// before
html_attr(['data-payload' => $rawBinary])
// after
html_attr(['data-payload' => base64_encode($rawBinary)])
Defensive patterns

Strategy: try-catch

Validate before calling

foreach ($values as $v) { if (!is_scalar($v) && !$v instanceof \Stringable && null !== $v) json_encode($v, JSON_THROW_ON_ERROR); }

Type guard

function isJsonEncodableAttr(mixed $v): bool { try { if (is_scalar($v) || $v instanceof \Stringable || null === $v) return true; json_encode($v, JSON_THROW_ON_ERROR); return true; } catch (\JsonException) { return false; } }

Try / catch

try { $html = html_attr($attrs); } catch (Twig\Error\RuntimeError $e) { if ($e->getPrevious() instanceof \JsonException) { /* sanitize and retry */ } throw $e; }

Prevention

When it happens

Trigger: Passing an array/object containing resources, circular references or invalid UTF-8 strings as an attribute value in html_attr().

Common situations: Binary data or malformed UTF-8 from legacy databases placed into attributes; recursively-referencing objects; DOMDocument or PDO objects accidentally passed as values.

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 twigphp/Twig@a414c3a491 (2026-09-13). Data as JSON: /api/errors/d043ef6d40ab4fe8. Report an issue: GitHub.

Appendix: source

Thrown at extra/html-extra/HtmlExtension.php:247

        }

        if (str_starts_with($name, 'aria-')) {
            // For aria-*, convert booleans to "true" and "false" strings
            if (true === $value) {
                $value = 'true';
            } elseif (false === $value) {
                $value = 'false';
            }
        }

        if (str_starts_with($name, 'data-')) {
            if (!$value instanceof AttributeValueInterface && !$value instanceof \Stringable && null !== $value && !\is_scalar($value)) {
                // ... encode non-null non-scalars as JSON, but leave the string representation
                // of a Stringable alone, as it is already the value the object asks to render as
                try {
                    $value = json_encode($value, \JSON_THROW_ON_ERROR);
                } catch (\JsonException $e) {
                    throw new RuntimeError(\sprintf('The "%s" attribute value cannot be JSON encoded.', $name), previous: $e);
                }
            } elseif (true === $value) {
                // ... and convert boolean true to a 'true'  string.
                $value = 'true';
            }
        }

        // Convert iterable values to token lists
        if (!$value instanceof AttributeValueInterface && is_iterable($value)) {
            if ('style' === $name) {
                $value = new InlineStyle($value);
            } else {
                $value = new SeparatedTokenList($value);
            }
        }

        if ($value instanceof AttributeValueInterface) {
            $value = $value->getValue();

View on GitHub (pinned to a414c3a491)