|null Null when $json is not a JSON object, or its top level * is one only nominally (keys "0", "1", … – see the class docblock): re-encoding those * keys as a PHP array would silently turn the object back into a JSON list. */ public static function decodeObject(string $json): ?array { try { $value = json_decode($json, false, 512, JSON_THROW_ON_ERROR); } catch (JsonException) { return null; } if (! $value instanceof stdClass) { return null; } $keys = get_object_vars($value); if ($keys !== [] && array_is_list($keys)) { return null; } $converted = self::fromJson($value); return is_array($converted) ? $converted : []; } /** Converts json_decode(…, false) output: objects become arrays unless that would lose their object-ness. */ public static function fromJson(mixed $value): mixed { if ($value instanceof stdClass) { $array = []; foreach (get_object_vars($value) as $key => $item) { $array[$key] = self::fromJson($item); } return $array === [] || array_is_list($array) ? (object) $array : $array; } if (is_array($value)) { return array_map([self::class, 'fromJson'], $value); } return $value; } /** * @throws JsonException When $value cannot be represented as JSON – including a string * with an invalid UTF-8 byte sequence (JSON_ERROR_UTF8). 2a only ever encodes * attributes this plugin already decoded from valid block markup, so that never * happens here; a caller that later encodes user-supplied strings (2b's writers) * must catch this and map it to a validation error instead of a fatal. */ public static function encode(mixed $value): string { // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode -- pure PHP (spec §4.4). return json_encode($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR); } }