false, 'json' => null, 'removed_positions' => [], 'reason' => '']; if ($json === '') { $base['reason'] = 'empty'; return $base; } // 本身就是合法 JSON,无需修复 if (self::isValidJson($json)) { $base['ok'] = true; $base['json'] = $json; return $base; } $current = $json; $removed = []; for ($attempt = 0; $attempt < 30; $attempt++) { $index = self::findExtraClosingBrace($current); if ($index === null) { $base['reason'] = 'unsupported-syntax'; $base['removed_positions'] = $removed; return $base; } $current = substr($current, 0, $index) . substr($current, $index + 1); $removed[] = $index; if (self::isValidJson($current)) { $base['ok'] = true; $base['json'] = $current; $base['removed_positions'] = $removed; return $base; } } $base['reason'] = 'too-many-attempts'; $base['removed_positions'] = $removed; return $base; } private static function isValidJson(string $json): bool { json_decode($json, true); return json_last_error() === JSON_ERROR_NONE; } /** * 定位"数组内一个元素结束后多写了一个 }"的字符位置;无匹配返回 null。 * * 实现说明:逐字符模拟 JSON 的括号/状态结构,字符串内容(含转义)完整跳过, * 不修改任何字符;仅当栈顶是数组、且该数组刚完成一个元素(期待 , 或 ])时 * 遇到 "}",并且其后能自然续接(]、}、, 或结尾)才认为这是多余的 "}"。 */ private static function findExtraClosingBrace(string $json): ?int { $len = strlen($json); // 栈元素: ['t' => '['|'{', 'p' => 'value'|'key'|'colon'|'comma'] $stack = []; $i = 0; while ($i < $len) { $ch = $json[$i]; if ($ch === ' ' || $ch === "\t" || $ch === "\n" || $ch === "\r") { $i++; continue; } if ($ch === '"') { // 字符串:可能是对象的 key,也可能是 value if (!self::isStringStartAllowed($stack)) { return null; } $i = self::skipString($json, $i, $len); if ($i >= $len) { return null; // 字符串未闭合,不在安全修复范围 } self::markValueDone($stack, true); $i++; continue; } if ($ch === '{' || $ch === '[') { if (!self::isValueStartAllowed($stack)) { return null; } $stack[] = ['t' => $ch, 'p' => $ch === '[' ? 'value' : 'key']; $i++; continue; } if ($ch === '}') { if (empty($stack)) { return null; } $topIndex = count($stack) - 1; $top = $stack[$topIndex]; if ($top['t'] === '[') { if ($top['p'] === 'comma') { // 数组元素结束后出现多余的 }:仅当后面能自然续接时才删除 $j = $i + 1; while ($j < $len && ($json[$j] === ' ' || $json[$j] === "\t" || $json[$j] === "\n" || $json[$j] === "\r")) { $j++; } if ($j >= $len || $json[$j] === ']' || $json[$j] === '}' || $json[$j] === ',') { return $i; } } return null; } // 对象闭合:空对象 {} 或 key:value 之后 if ($top['p'] === 'key' || $top['p'] === 'comma') { array_pop($stack); self::markValueDone($stack, false); $i++; continue; } return null; // 冒号后缺值等,不在安全修复范围 } if ($ch === ']') { if (empty($stack)) { return null; } $topIndex = count($stack) - 1; $top = $stack[$topIndex]; if ($top['t'] === '[' && ($top['p'] === 'value' || $top['p'] === 'comma')) { array_pop($stack); self::markValueDone($stack, false); $i++; continue; } return null; } if ($ch === ',') { if (empty($stack)) { return null; } $topIndex = count($stack) - 1; if ($stack[$topIndex]['p'] === 'comma') { $stack[$topIndex]['p'] = $stack[$topIndex]['t'] === '[' ? 'value' : 'key'; $i++; continue; } return null; } if ($ch === ':') { if (empty($stack)) { return null; } $topIndex = count($stack) - 1; if ($stack[$topIndex]['t'] === '{' && $stack[$topIndex]['p'] === 'colon') { $stack[$topIndex]['p'] = 'value'; $i++; continue; } return null; } // 字面量 token(数字 / true / false / null) if (!self::isValueStartAllowed($stack)) { return null; } $i++; while ($i < $len) { $c = $json[$i]; if ($c === ' ' || $c === "\t" || $c === "\n" || $c === "\r" || $c === ',' || $c === ']' || $c === '}' || $c === ':' || $c === '"' || $c === '{' || $c === '[') { break; } $i++; } self::markValueDone($stack, false); } // 完整扫描未发现可安全修复的多余 } return null; } /** * 跳过一段 JSON 字符串(含转义),返回闭合引号的位置;未闭合返回 $len。 */ private static function skipString(string $json, int $i, int $len): int { $i++; while ($i < $len) { $c = $json[$i]; if ($c === '\\') { $i += 2; continue; } if ($c === '"') { return $i; } $i++; } return $len; } /** * 值(或对象的 key 字符串)是否允许在此位置开始。 */ private static function isStringStartAllowed(array $stack): bool { if (empty($stack)) { return true; } $top = end($stack); if ($top['t'] === '[') { return $top['p'] === 'value'; } // 对象内:期待 key(字符串)或冒号后的 value return $top['p'] === 'key' || $top['p'] === 'value'; } /** * 非字符串值(嵌套容器、数字、true/false/null)是否允许开始。 */ private static function isValueStartAllowed(array $stack): bool { if (empty($stack)) { return true; } $top = end($stack); if ($top['t'] === '[') { return $top['p'] === 'value'; } // 对象内非字符串值只能是冒号后的 value return $top['p'] === 'value'; } /** * 一个值(字符串/字面量/容器结束)之后,标记所在层进入"期待分隔符或闭合"状态。 * * @param bool $stringToken 是否为字符串 token(字符串在对象 key 位置时是 key) */ private static function markValueDone(array &$stack, bool $stringToken): void { if (empty($stack)) { return; } $topIndex = count($stack) - 1; if ($stringToken && $stack[$topIndex]['t'] === '{' && $stack[$topIndex]['p'] === 'key') { // key 字符串结束,期待冒号 $stack[$topIndex]['p'] = 'colon'; return; } $stack[$topIndex]['p'] = 'comma'; } }