{"id":33374425,"url":"https://github.com/jordicor/ai-json-cleanroom-php","last_synced_at":"2026-04-13T14:32:52.376Z","repository":{"id":325026574,"uuid":"1099513511","full_name":"jordicor/ai-json-cleanroom-php","owner":"jordicor","description":"PHP. Automatically extracts JSON from markdown/text, repairs common AI mistakes, validates structure. Returns clean data when successful, detailed feedback for retries when not.","archived":false,"fork":false,"pushed_at":"2025-11-19T05:43:37.000Z","size":59,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-11-19T07:18:21.473Z","etag":null,"topics":["ai","ai-tools","claude","composer","gpt","json","json-parser","json-repair","json-validation","laravel","openai","php8","structured-output","structured-output-parser","symfony"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jordicor.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-19T04:56:34.000Z","updated_at":"2025-11-19T05:43:41.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jordicor/ai-json-cleanroom-php","commit_stats":null,"previous_names":["jordicor/ai-json-cleanroom-php"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/jordicor/ai-json-cleanroom-php","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jordicor%2Fai-json-cleanroom-php","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jordicor%2Fai-json-cleanroom-php/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jordicor%2Fai-json-cleanroom-php/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jordicor%2Fai-json-cleanroom-php/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jordicor","download_url":"https://codeload.github.com/jordicor/ai-json-cleanroom-php/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jordicor%2Fai-json-cleanroom-php/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":285873538,"owners_count":27246054,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-22T02:00:05.934Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","ai-tools","claude","composer","gpt","json","json-parser","json-repair","json-validation","laravel","openai","php8","structured-output","structured-output-parser","symfony"],"created_at":"2025-11-22T23:01:54.108Z","updated_at":"2025-11-22T23:01:54.747Z","avatar_url":"https://github.com/jordicor.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# AI JSON Cleanroom (PHP)\n\n![PHP](https://img.shields.io/badge/php-8.1+-777BB4.svg)\n![License](https://img.shields.io/badge/license-MIT-green.svg)\n\n**Your AI returns broken JSON? Put this in between.**\n\nWorks with any AI model: ChatGPT, Claude, Gemini, Llama. Zero dependencies beyond PHP standard library.\n\nAutomatically extracts JSON from markdown/text, repairs common AI mistakes, validates structure.\nReturns clean data when successful, detailed feedback for retries when not.\n\n**This is the PHP port of [AI JSON Cleanroom](https://github.com/jordicor/ai-json-cleanroom).**\n\n**Quick Links:** [Fast Track (2 min)](#fast-track-integration-in-3-steps) • [Why This Tool?](#why-you-need-this) • [Code Example](#quick-start) • [Install](#installation) • [Configuration Guide](#understanding-the-configuration-options) • [Troubleshooting](#troubleshooting-guide) • [Integrations](#real-world-integrations) • [Full Documentation ↓](#features-overview)\n\n---\n\n## Fast Track: Integration in 3 Steps\n\n**Want to start using this right away?** Here's how:\n\n1. **Download** the `ai_json_cleanroom.php` file to your project\n2. **Include it** in your code: `require_once 'ai_json_cleanroom.php';`\n3. **Done.** Start processing AI responses through `validate_ai_json()`\n\nReady in 2 minutes. Works immediately.\n\n[Show me the code →](#quick-start) • [Why do I need this? →](#why-you-need-this)\n\n---\n\n## Why You Need This\n\n**The situation:** You request JSON from your AI. Sometimes you receive:\n\n| What you get | What breaks |\n|-------------|-------------|\n| `Sure! Here's the JSON: {\"name\": \"Alice\"}` | Extra text crashes `json_decode()` |\n| `{'name': 'Alice'}` | Single quotes instead of double quotes |\n| `{\"users\": [{\"id\": 1}, {\"i` | Truncated mid-response (token limit) |\n\n**Current solution:** Try/catch blocks, regex patterns, manual fixes, repeated API calls.\n\n**This tool:** Handles all cases automatically. One function call.\n\n---\n\n## Installation\n\n### Via Composer (Recommended)\n\n```bash\ncomposer require jordicor/ai-json-cleanroom-php\n```\n\n### Manual Installation\n\nDownload `ai_json_cleanroom.php` to your project:\n\n```bash\nwget https://raw.githubusercontent.com/jordicor/ai-json-cleanroom-php/main/ai_json_cleanroom.php\n```\n\nThen include it:\n\n```php\n\u003c?php\nrequire_once 'ai_json_cleanroom.php';\n```\n\n**Requirements:**\n- PHP 8.1 or higher\n- ext-mbstring (for proper UTF-8 handling)\n- ext-json\n\n**Ready.** Start using: `validate_ai_json($response)`\n\n---\n\n## Quick Start\n\n```php\n\u003c?php\nrequire_once 'ai_json_cleanroom.php';\n\n// Anything your AI returns (messy, wrapped, incomplete)\n$aiResponse = \"Here's your data:\\n```json\\n{'name': 'Alice', age: 30}  // Invalid JSON syntax\\n```\\n\";\n\n// One line to clean and validate\n$result = validate_ai_json($aiResponse);\n\nif ($result-\u003ejsonValid) {\n    print_r($result-\u003edata);  // Clean: ['name' =\u003e 'Alice', 'age' =\u003e 30]\n} else {\n    print_r($result-\u003eerrors);  // Detailed error information\n}\n```\n\n**Done.** No configuration needed. It works out of the box.\n\nCheck `$result-\u003ewarnings` to see what was fixed automatically.\n\n---\n\n### What Just Happened?\n\nThe cleaner automatically:\n- Found the JSON inside markdown code fence\n- Fixed single quotes to double quotes\n- Added quotes to the unquoted key `age`\n- Removed the inline comment\n- Validated the final structure\n\nProcessing time: ~1ms. Zero configuration required.\n\n**Useful tip:** Check `$result-\u003elikelyTruncated` to detect when the AI hit its token limit. This saves unnecessary retry API calls.\n\n---\n\n## You're All Set\n\n**That's everything you need.** The tool works immediately with smart defaults.\n\nEverything below is optional documentation for:\n- Understanding how the tool works internally\n- Advanced configuration options\n- Framework integrations (Laravel, Symfony, etc.)\n- Your AI assistant to read and understand the full API\n\n**For most users:** The sections above are sufficient. Start building.\n\n**Want to learn more?** Continue reading below.\n\n**💡 Found this useful?** Star the repo ⭐ to help others discover it!\n\n---\n\n## Features Overview\n\n### 1. Smart Extraction\n\nAutomatically extracts JSON from various formats:\n\n```php\n// From markdown code fence\n$markdown = 'Here is the data:\\n```json\\n{\"status\": \"success\"}\\n```\\n';\n$result = validate_ai_json($markdown);\n// Extracted: [\"status\" =\u003e \"success\"]\n\n// From mixed text\n$mixed = 'The result is {\"status\": \"success\"} as requested.';\n$result = validate_ai_json($mixed);\n// Extracted: [\"status\" =\u003e \"success\"]\n```\n\n### 2. Conservative Repair\n\nFixes common AI mistakes with configurable safeguards:\n\n```php\n// Single quotes → double quotes\n$result = validate_ai_json(\"{'name': 'Alice'}\");\n// Repaired: [\"name\" =\u003e \"Alice\"]\n\n// Boolean constants (True/False/None) → JSON\n$result = validate_ai_json('{\"active\": True, \"value\": None}');\n// Repaired: [\"active\" =\u003e true, \"value\" =\u003e null]\n\n// Unquoted keys → quoted keys\n$result = validate_ai_json('{name: \"Alice\", age: 30}');\n// Repaired: [\"name\" =\u003e \"Alice\", \"age\" =\u003e 30]\n\n// Comments removal\n$result = validate_ai_json('{\n  \"name\": \"Alice\",  // user name\n  /* age field */ \"age\": 30\n}');\n// Repaired: [\"name\" =\u003e \"Alice\", \"age\" =\u003e 30]\n```\n\n**Safeguards:**\n- Maximum modifications limit (default: 200 changes or 2% of input size)\n- Disabled if truncation detected\n- Incremental parse-check after each repair pass\n- Detailed repair metadata in `$result-\u003einfo`\n\n### 3. Truncation Detection\n\nIdentifies incomplete outputs before wasting retries:\n\n```php\n$truncated = '{\"users\": [{\"name\": \"Alice\", \"age\": 30}, {\"name\": \"Bob\", \"age\":';\n\n$result = validate_ai_json($truncated);\necho $result-\u003elikelyTruncated;  // true\necho $result-\u003eerrors[0]-\u003emessage;\n// \"No JSON payload found in input.\"\nprint_r($result-\u003eerrors[0]-\u003edetail);\n// ['truncation_reasons' =\u003e ['unclosed_braces_or_brackets', 'suspicious_trailing_character']]\n```\n\n**Detection signals:**\n- Unclosed strings\n- Unbalanced braces/brackets\n- Suspicious trailing characters (`,`, `:`, `{`, `[`)\n- Ellipsis at end (`...`)\n\n### 4. Schema Validation\n\nValidate against JSON Schema subset:\n\n```php\n$schema = [\n    \"type\" =\u003e \"object\",\n    \"required\" =\u003e [\"name\", \"email\"],\n    \"properties\" =\u003e [\n        \"name\" =\u003e [\n            \"type\" =\u003e \"string\",\n            \"minLength\" =\u003e 1,\n            \"maxLength\" =\u003e 100\n        ],\n        \"email\" =\u003e [\n            \"type\" =\u003e \"string\",\n            \"pattern\" =\u003e '/^[\\w\\.-]+@[\\w\\.-]+\\.\\w+$/'\n        ],\n        \"age\" =\u003e [\n            \"type\" =\u003e \"integer\",\n            \"minimum\" =\u003e 0,\n            \"maximum\" =\u003e 150\n        ]\n    ],\n    \"additionalProperties\" =\u003e false\n];\n\n$result = validate_ai_json($aiOutput, schema: $schema);\n\nif (!$result-\u003ejsonValid) {\n    foreach ($result-\u003eerrors as $error) {\n        echo \"{$error-\u003ecode}: {$error-\u003emessage} at {$error-\u003epath}\\n\";\n    }\n}\n```\n\n**Supported schema keywords:**\n- Types: `object`, `array`, `string`, `number`, `integer`, `boolean`, `null`\n- Object: `required`, `properties`, `patternProperties`, `additionalProperties`\n- Array: `items`, `additionalItems`, `minItems`, `maxItems`, `uniqueItems`\n- String: `minLength`, `maxLength`, `pattern`\n- Number: `minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`, `multipleOf`\n- Combinators: `anyOf`, `oneOf`, `allOf`\n- Constraints: `enum`, `const`, `allow_empty`\n\n### 5. Path-Based Expectations\n\nValidate specific paths with wildcard support:\n\n```php\n$expectations = [\n    [\n        \"path\" =\u003e \"users[*].email\",\n        \"required\" =\u003e true,\n        \"pattern\" =\u003e '/^[\\w\\.-]+@[\\w\\.-]+\\.\\w+$/'\n    ],\n    [\n        \"path\" =\u003e \"users[*].status\",\n        \"required\" =\u003e true,\n        \"in\" =\u003e [\"active\", \"pending\", \"inactive\"]\n    ],\n    [\n        \"path\" =\u003e \"metadata.version\",\n        \"required\" =\u003e true,\n        \"type\" =\u003e \"string\",\n        \"pattern\" =\u003e '/^\\d+\\.\\d+\\.\\d+$/'\n    ]\n];\n\n$result = validate_ai_json($aiOutput, expectations: $expectations);\n```\n\n### 6. Non-Throwing API\n\nAlways returns a `ValidationResult` - never crashes:\n\n```php\n$result = validate_ai_json($anyInput);\n\n// Always safe to access\necho \"Valid: \" . ($result-\u003ejsonValid ? 'yes' : 'no') . \"\\n\";\necho \"Truncated: \" . ($result-\u003elikelyTruncated ? 'yes' : 'no') . \"\\n\";\necho \"Errors: \" . count($result-\u003eerrors) . \"\\n\";\necho \"Warnings: \" . count($result-\u003ewarnings) . \"\\n\";\nprint_r($result-\u003edata);  // null if invalid\nprint_r($result-\u003einfo);  // Extraction/parsing metadata\n\n// Structured error handling\nforeach ($result-\u003eerrors as $error) {\n    echo \"Code: {$error-\u003ecode}\\n\";\n    echo \"Path: {$error-\u003epath}\\n\";\n    echo \"Message: {$error-\u003emessage}\\n\";\n    print_r($error-\u003edetail);\n}\n```\n\n---\n\n## Understanding the Configuration Options\n\nNot sure which options to enable? This guide explains each repair strategy with practical examples.\n\n### When to Use Each Repair Strategy\n\n#### `fixSingleQuotes` (Default: true)\n**What it does:** Converts single quotes `'text'` to JSON-compliant double quotes `\"text\"`\n\n**When to keep it ON:**\n- Working with AI models that output single-quoted strings\n- Processing outputs from code-generation models\n- General use - this is safe and commonly needed\n\n**When to turn it OFF:**\n- Your AI model never uses single quotes (rare)\n- You're processing pure JSON from a non-AI source\n\n**Example scenario:**\n```php\n// GPT often returns this mix:\n$input = \"{'name': 'Alice', \\\"age\\\": 30}\";  // Mixed quotes\n\n// With fixSingleQuotes = true:\n// ✅ Becomes: {\"name\": \"Alice\", \"age\": 30}\n\n// With fixSingleQuotes = false:\n// ❌ Parse fails on single quotes\n```\n\n#### `quoteUnquotedKeys` (Default: true)\n**What it does:** Adds quotes to JavaScript-style unquoted object keys\n\n**When to keep it ON:**\n- Working with models trained on JavaScript/TypeScript code\n- Processing outputs that might include object literals\n- Claude models (sometimes output JS-style objects)\n\n**When to turn it OFF:**\n- Strict JSON-only environment\n- You want to detect and reject JS-style syntax\n\n**Real-world example:**\n```php\n// Claude sometimes returns:\n$input = \"{name: 'Alice', age: 30, active: true}\";\n\n// With quoteUnquotedKeys = true:\n// ✅ Becomes: {\"name\": \"Alice\", \"age\": 30, \"active\": true}\n```\n\n#### `replaceConstants` (Default: true)\n**What it does:** Converts capitalized boolean constants (`True`/`False`/`None`) to JSON (`true`/`false`/`null`)\n\n**When to keep it ON:**\n- Always, unless you have a specific reason not to\n- Essential for AI models that output capitalized booleans\n\n**Example:**\n```php\n// AI models sometimes output capitalized booleans:\n$input = '{\"active\": True, \"deleted\": False, \"parent\": None}';\n\n// With replaceConstants = true:\n// ✅ Becomes: {\"active\": true, \"deleted\": false, \"parent\": null}\n```\n\n#### `stripJsComments` (Default: true)\n**What it does:** Removes JavaScript-style comments (`//` and `/* */`)\n\n**When to keep it ON:**\n- Models that explain their JSON with comments\n- When processing configuration-style outputs\n\n**Example:**\n```php\n$input = \u003c\u003c\u003c'JSON'\n{\n  \"name\": \"Alice\",  // user name\n  /* age field */ \"age\": 30\n}\nJSON;\n// ✅ Comments are safely removed\n```\n\n#### `normalizeCurlyQuotes` (Default: \"always\")\n**What it does:** Handles smart/typographic quotes that break JSON parsing\n\n**Options:**\n- `\"always\"` - Convert smart quotes before parsing (safest)\n- `\"auto\"` - Only convert if initial parse fails (balanced approach)\n- `\"never\"` - Keep smart quotes as-is (when you want to preserve them)\n\n**When to use each:**\n- `\"always\"`: Default choice, handles copy-paste from documents\n- `\"auto\"`: When performance matters and smart quotes are rare\n- `\"never\"`: When processing content where quote style matters\n\n**Example:**\n```php\n// From copy-paste or models trained on web text:\n$input = '{\"text\": \"She said \"hello\" to me\"}';  // Smart quotes\n\n// With normalizeCurlyQuotes = \"always\":\n// ✅ Becomes: {\"text\": \"She said \\\"hello\\\" to me\"}\n```\n\n#### `enableSafeRepairs` (Default: true)\n**What it does:** Master toggle for all repair strategies\n\n**When to turn OFF:**\n- You want to validate only, not repair\n- Debugging to see raw parsing errors\n- You have your own repair logic\n\n#### `maxTotalRepairs` and `maxRepairsPercent` (Defaults: 200, 0.02)\n**What they do:** Safety limits to prevent over-correction\n\n**When to increase:**\n- Very messy outputs from older models\n- Known high-error scenarios\n\n**When to decrease:**\n- You want stricter validation\n- Suspicious of too many modifications\n\n**Example configuration:**\n```php\n// For very messy outputs:\n$options = new ValidateOptions();\n$options-\u003emaxTotalRepairs = 500;      // Allow more fixes\n$options-\u003emaxRepairsPercent = 0.05;   // Allow 5% of content to be modified\n\n// For strict validation:\n$options = new ValidateOptions();\n$options-\u003emaxTotalRepairs = 10;       // Minimal fixes only\n$options-\u003emaxRepairsPercent = 0.001;  // Less than 0.1% modifications\n```\n\n\u003e 📝 **Note:** Start with defaults. They're battle-tested on thousands of real AI outputs. Only adjust if you have specific issues.\n\n---\n\n## Common Scenarios \u0026 Solutions\n\n### Scenario 1: \"My AI model keeps adding explanations\"\n\n**The Problem:** You explicitly ask for JSON only, but get:\n```\nI'll help you with that! Here's the JSON data:\n{\"status\": \"success\"}\nLet me know if you need anything else!\n```\n\n**The Solution:**\n```php\n// Cleanroom automatically extracts the JSON part\n$result = validate_ai_json($chattyResponse);\nprint_r($result-\u003edata);  // Just the JSON: [\"status\" =\u003e \"success\"]\necho $result-\u003einfo['source'];  // Tells you where it found it: 'balanced_block'\n```\n\n### Scenario 2: \"Token limits are cutting off my JSON\"\n\n**The Problem:** Large responses get truncated:\n```json\n{\"users\": [{\"id\": 1, \"name\": \"Alice\"}, {\"id\": 2, \"na\n```\n\n**The Solution:**\n```php\n$result = validate_ai_json($truncatedResponse);\n\nif ($result-\u003elikelyTruncated) {\n    // You know exactly what happened\n    echo \"Response truncated - reasons: \";\n    print_r($result-\u003eerrors[0]-\u003edetail['truncation_reasons']);\n    // Output: ['unclosed_braces_or_brackets', 'unterminated_string']\n\n    // Smart retry with higher token limit\n    retryWithHigherLimit();\n}\n```\n\n### Scenario 3: \"Mixed quote styles are breaking everything\"\n\n**The Problem:** Your AI model uses single quotes instead of valid JSON double quotes:\n```php\n$output = \"{'users': [\\\"Alice\\\", \\\"Bob\\\"], 'count': 2}\";\n```\n\n**The Solution:**\n```php\n$result = validate_ai_json($output);\n// Automatically fixes to: [\"users\" =\u003e [\"Alice\", \"Bob\"], \"count\" =\u003e 2]\n```\n\n### Scenario 4: \"I need to validate specific fields exist\"\n\n**The Problem:** You need certain fields but don't want full schema validation.\n\n**The Solution:** Use path expectations:\n```php\n$expectations = [\n    [\"path\" =\u003e \"users[*].email\", \"required\" =\u003e true],\n    [\"path\" =\u003e \"metadata.version\", \"pattern\" =\u003e '/^\\d+\\.\\d+\\.\\d+$/']\n];\n\n$result = validate_ai_json($aiOutput, expectations: $expectations);\n// Validates that all users have emails and version is semver\n```\n\n### Scenario 5: \"The JSON has comments and I want to keep the information\"\n\n**The Problem:** AI model adds helpful comments that contain important context:\n```json\n{\n  \"temperature\": 0.7,  // Higher for creativity\n  \"max_tokens\": 100   // Keep responses concise\n}\n```\n\n**The Solution:**\n```php\n// First, extract with comments preserved to see them\n$rawResponse = $aiOutput;\n\n// Clean for parsing\n$result = validate_ai_json($rawResponse);\n\n// The comments are removed for valid JSON\nprint_r($result-\u003edata);  // [\"temperature\" =\u003e 0.7, \"max_tokens\" =\u003e 100]\n\n// If you need the comments, parse them separately from $rawResponse\n```\n\n### Scenario 6: \"Different AI models fail in different ways\"\n\n**The Problem:** GPT may use single quotes and unquoted keys, Claude wraps in markdown, Gemini may truncate.\n\n**The Solution:** One configuration handles all:\n```php\n// Same code for ALL models\nfunction cleanAnyAiOutput(string $output): array\n{\n    $result = validate_ai_json($output);  // Default options handle everything\n\n    if ($result-\u003ejsonValid) {\n        return $result-\u003edata;\n    } elseif ($result-\u003elikelyTruncated) {\n        throw new RuntimeException(\"Output truncated - increase token limit\");\n    } else {\n        $errorMsg = implode(\", \", array_map(fn($e) =\u003e $e-\u003emessage, $result-\u003eerrors));\n        throw new RuntimeException(\"Could not parse: {$errorMsg}\");\n    }\n}\n\n// Works with GPT, Claude, Gemini, Llama, etc.\n```\n\n\u003e ⚠️ **Important:** Truncation detection always runs first. If JSON is truncated, repairs are skipped to avoid corrupting partial data.\n\n---\n\n## Troubleshooting Guide\n\n### \"Why isn't my JSON being repaired?\"\n\n**Possible causes and solutions:**\n\n1. **Truncation detected**\n   - Cleanroom disables repairs for truncated input (safety measure)\n   - Solution: Get complete output first, then retry\n\n2. **Repair limit reached**\n   - Default limit: 200 changes or 2% of input size\n   - Solution: Increase limits if needed:\n   ```php\n   $options = new ValidateOptions();\n   $options-\u003emaxTotalRepairs = 500;  // Raise limit\n   $options-\u003emaxRepairsPercent = 0.05;  // Allow 5% modifications\n   ```\n\n3. **Specific repair disabled**\n   - Check your options - maybe `fixSingleQuotes = false`?\n   - Solution: Enable the specific repair you need\n\n### \"The parser says JSON is invalid but it looks fine to me\"\n\n**Common hidden issues:**\n- Invisible Unicode characters (zero-width spaces, etc.)\n- Smart quotes from copy-paste: `\"text\"` vs `\"text\"`\n- Line breaks inside strings without proper escaping\n\n**Diagnosis:**\n```php\n$result = validate_ai_json($yourInput, options: new ValidateOptions([\n    'normalizeCurlyQuotes' =\u003e 'always'  // Fixes smart quotes\n]));\nprint_r($result-\u003eerrors);  // See specific character positions\n```\n\n### \"It works with GPT but fails with Claude\"\n\n**Issue:** Different models have different quirks.\n\n**Solution:** Check the extraction source:\n```php\n$result = validate_ai_json($claudeOutput);\necho \"Found JSON in: {$result-\u003einfo['source']}\\n\";\n// 'code_fence' = markdown block\n// 'balanced_block' = found in text\n// 'raw' = was already clean\n```\n\n### \"Performance is slow with large outputs\"\n\n**Solutions:**\n1. **Disable unnecessary repairs:**\n   ```php\n   $options = new ValidateOptions();\n   $options-\u003estripJsComments = false;  // If you never have comments\n   $options-\u003enormalizeCurlyQuotes = 'never';  // If you never have smart quotes\n   ```\n\n2. **Use opcache** (PHP's bytecode cache):\n   ```php\n   // Check if opcache is enabled\n   echo opcache_get_status()['opcache_enabled'] ? 'Enabled' : 'Disabled';\n   ```\n\n### \"I want to see what was changed\"\n\n**Solution:** Check warnings and info:\n```php\n$result = validate_ai_json($messyJson);\n\n// See all repairs applied\nforeach ($result-\u003ewarnings as $warning) {\n    if ($warning-\u003ecode === ErrorCode::REPAIRED) {\n        echo \"Repairs applied: \" . implode(\", \", $warning-\u003edetail['applied']) . \"\\n\";\n        echo \"Number of changes: \";\n        print_r($warning-\u003edetail['counts']);\n    }\n}\n\n// See extraction details\necho \"Extraction method: {$result-\u003einfo['source']}\\n\";\necho \"Parser used: {$result-\u003einfo['parse_backend']}\\n\";\n```\n\n### \"Schema validation is rejecting valid data\"\n\n**Common issues:**\n1. **Pattern escaping:** Remember to use delimiters in PHP regex: `'/^\\d+$/'` not `'^\\d+$'`\n2. **Type mismatches:** JSON numbers include floats - use `\"type\" =\u003e \"number\"` not `\"integer\"` unless you're sure\n3. **Required fields:** Double-check field names are exact matches\n\n**Debug approach:**\n```php\n// Start without schema to see actual structure\n$result = validate_ai_json($output);\nprint_r($result-\u003edata);\n\n// Then add schema gradually\n$schema = [\"type\" =\u003e \"object\"];  // Start simple\n// Add requirements one by one\n```\n\n### \"mbstring extension not found\"\n\n**Issue:** PHP complains about missing mbstring functions.\n\n**Solution:**\n```bash\n# Ubuntu/Debian\nsudo apt-get install php-mbstring\n\n# macOS with Homebrew\nbrew install php\n# (mbstring is included by default)\n\n# Windows\n# Enable in php.ini:\nextension=mbstring\n\n# Verify installation\nphp -m | grep mbstring\n```\n\n---\n\n## Real-World Integrations\n\n### With OpenAI API\n\n```php\n\u003c?php\nrequire_once 'ai_json_cleanroom.php';\n\n$apiKey = getenv('OPENAI_API_KEY');\n$client = new \\GuzzleHttp\\Client();\n\n$response = $client-\u003epost('https://api.openai.com/v1/chat/completions', [\n    'headers' =\u003e [\n        'Authorization' =\u003e \"Bearer {$apiKey}\",\n        'Content-Type' =\u003e 'application/json',\n    ],\n    'json' =\u003e [\n        'model' =\u003e 'gpt-5.1',\n        'messages' =\u003e [\n            ['role' =\u003e 'system', 'content' =\u003e 'You are a helpful assistant that outputs JSON.'],\n            ['role' =\u003e 'user', 'content' =\u003e 'Generate user profile for Alice Johnson, age 30']\n        ],\n        'response_format' =\u003e ['type' =\u003e 'json_object']\n    ]\n]);\n\n$data = json_decode($response-\u003egetBody(), true);\n$aiOutput = $data['choices'][0]['message']['content'];\n\n// Clean and validate\n$result = validate_ai_json(\n    $aiOutput,\n    schema: [\n        'type' =\u003e 'object',\n        'required' =\u003e ['name', 'age'],\n        'properties' =\u003e [\n            'name' =\u003e ['type' =\u003e 'string'],\n            'age' =\u003e ['type' =\u003e 'integer', 'minimum' =\u003e 0]\n        ]\n    ]\n);\n\nif ($result-\u003ejsonValid) {\n    $userData = $result-\u003edata;\n    echo \"User: {$userData['name']}, Age: {$userData['age']}\\n\";\n} else {\n    echo \"Validation failed:\\n\";\n    foreach ($result-\u003eerrors as $error) {\n        echo \"- {$error-\u003emessage}\\n\";\n    }\n}\n```\n\n### With Anthropic Claude\n\n```php\n\u003c?php\nrequire_once 'ai_json_cleanroom.php';\n\n$apiKey = getenv('ANTHROPIC_API_KEY');\n$client = new \\GuzzleHttp\\Client();\n\n$response = $client-\u003epost('https://api.anthropic.com/v1/messages', [\n    'headers' =\u003e [\n        'x-api-key' =\u003e $apiKey,\n        'anthropic-version' =\u003e '2023-06-01',\n        'Content-Type' =\u003e 'application/json',\n    ],\n    'json' =\u003e [\n        'model' =\u003e 'claude-haiku-4-5',\n        'max_tokens' =\u003e 1024,\n        'messages' =\u003e [\n            [\n                'role' =\u003e 'user',\n                'content' =\u003e 'Generate a JSON object with user info for Alice, age 30'\n            ]\n        ]\n    ]\n]);\n\n$data = json_decode($response-\u003egetBody(), true);\n$aiOutput = $data['content'][0]['text'];\n\n// Claude might return:\n// \"Here's the user data:\\n```json\\n{\\\"name\\\": \\\"Alice\\\", \\\"age\\\": 30}\\n```\\nLet me know if you need anything else!\"\n\n$result = validate_ai_json($aiOutput);\n\nif ($result-\u003ejsonValid) {\n    echo \"Extracted data:\\n\";\n    print_r($result-\u003edata);\n    echo \"Extraction source: {$result-\u003einfo['source']}\\n\";  // 'code_fence'\n} else {\n    if ($result-\u003elikelyTruncated) {\n        echo \"Response was truncated, increasing max_tokens...\\n\";\n    } else {\n        echo \"Validation errors:\\n\";\n        print_r($result-\u003eerrors);\n    }\n}\n```\n\n### Retry Logic with Structured Feedback\n\n```php\n\u003c?php\nrequire_once 'ai_json_cleanroom.php';\n\nfunction generateWithRetry(string $prompt, array $schema, int $maxRetries = 3): ?array\n{\n    for ($attempt = 0; $attempt \u003c $maxRetries; $attempt++) {\n        $aiOutput = callAiApi($prompt);  // Your AI API call\n\n        $result = validate_ai_json($aiOutput, schema: $schema);\n\n        if ($result-\u003ejsonValid) {\n            return $result-\u003edata;\n        }\n\n        // Build feedback for retry\n        if ($result-\u003elikelyTruncated) {\n            $prompt .= \"\\n\\nIMPORTANT: Your previous response was truncated. Please ensure the complete JSON is returned.\";\n        } else {\n            $errorMessages = array_map(\n                fn($e) =\u003e \"- {$e-\u003epath}: {$e-\u003emessage}\",\n                $result-\u003eerrors\n            );\n            $feedback = implode(\"\\n\", $errorMessages);\n            $prompt .= \"\\n\\nYour previous JSON had these issues:\\n{$feedback}\\n\\nPlease fix these and return valid JSON.\";\n        }\n    }\n\n    throw new RuntimeException(\"Failed to generate valid JSON after {$maxRetries} attempts\");\n}\n\n// Usage\n$schema = [\n    'type' =\u003e 'object',\n    'required' =\u003e ['name', 'email', 'age'],\n    'properties' =\u003e [\n        'name' =\u003e ['type' =\u003e 'string'],\n        'email' =\u003e ['type' =\u003e 'string', 'pattern' =\u003e '/^[\\w\\.-]+@[\\w\\.-]+\\.\\w+$/'],\n        'age' =\u003e ['type' =\u003e 'integer', 'minimum' =\u003e 0]\n    ]\n];\n\n$userData = generateWithRetry(\n    'Generate a user profile for Alice Johnson',\n    $schema\n);\nprint_r($userData);\n```\n\n### With Laravel Framework\n\n```php\n\u003c?php\n\nnamespace App\\Services;\n\nuse Illuminate\\Support\\Facades\\Http;\n\nclass AiJsonService\n{\n    public function generateUserProfile(string $prompt): array\n    {\n        // Call AI API using Laravel HTTP client\n        $response = Http::withHeaders([\n            'Authorization' =\u003e 'Bearer ' . config('services.openai.key'),\n        ])-\u003epost('https://api.openai.com/v1/chat/completions', [\n            'model' =\u003e 'gpt-5.1',\n            'messages' =\u003e [\n                ['role' =\u003e 'user', 'content' =\u003e $prompt]\n            ]\n        ]);\n\n        $aiOutput = $response-\u003ejson()['choices'][0]['message']['content'];\n\n        // Clean and validate with ai-json-cleanroom\n        $result = validate_ai_json(\n            $aiOutput,\n            schema: [\n                'type' =\u003e 'object',\n                'required' =\u003e ['name', 'email'],\n                'properties' =\u003e [\n                    'name' =\u003e ['type' =\u003e 'string'],\n                    'email' =\u003e ['type' =\u003e 'string', 'pattern' =\u003e '/^[\\w\\.-]+@[\\w\\.-]+\\.\\w+$/']\n                ]\n            ]\n        );\n\n        if (!$result-\u003ejsonValid) {\n            // Log validation errors\n            \\Log::warning('AI JSON validation failed', [\n                'errors' =\u003e array_map(fn($e) =\u003e $e-\u003emessage, $result-\u003eerrors),\n                'truncated' =\u003e $result-\u003elikelyTruncated\n            ]);\n\n            throw new \\RuntimeException('Invalid AI response');\n        }\n\n        return $result-\u003edata;\n    }\n}\n```\n\n**Usage in Laravel controller:**\n```php\n\u003c?php\n\nnamespace App\\Http\\Controllers;\n\nuse App\\Services\\AiJsonService;\nuse Illuminate\\Http\\JsonResponse;\n\nclass UserController extends Controller\n{\n    public function __construct(private AiJsonService $aiService)\n    {\n    }\n\n    public function generateProfile(): JsonResponse\n    {\n        try {\n            $userData = $this-\u003eaiService-\u003egenerateUserProfile(\n                'Generate a user profile for Alice Johnson'\n            );\n\n            return response()-\u003ejson([\n                'success' =\u003e true,\n                'data' =\u003e $userData\n            ]);\n        } catch (\\Exception $e) {\n            return response()-\u003ejson([\n                'success' =\u003e false,\n                'error' =\u003e $e-\u003egetMessage()\n            ], 422);\n        }\n    }\n}\n```\n\n### With Symfony Framework\n\n```php\n\u003c?php\n\nnamespace App\\Service;\n\nuse Symfony\\Contracts\\HttpClient\\HttpClientInterface;\n\nclass AiJsonProcessor\n{\n    public function __construct(\n        private HttpClientInterface $httpClient,\n        private string $apiKey\n    ) {\n    }\n\n    public function processAiResponse(string $prompt): array\n    {\n        // Make API call using Symfony HTTP client\n        $response = $this-\u003ehttpClient-\u003erequest('POST',\n            'https://api.anthropic.com/v1/messages',\n            [\n                'headers' =\u003e [\n                    'x-api-key' =\u003e $this-\u003eapiKey,\n                    'anthropic-version' =\u003e '2023-06-01',\n                    'Content-Type' =\u003e 'application/json',\n                ],\n                'json' =\u003e [\n                    'model' =\u003e 'claude-haiku-4-5',\n                    'max_tokens' =\u003e 1024,\n                    'messages' =\u003e [\n                        ['role' =\u003e 'user', 'content' =\u003e $prompt]\n                    ]\n                ]\n            ]\n        );\n\n        $data = $response-\u003etoArray();\n        $aiOutput = $data['content'][0]['text'];\n\n        // Clean and validate\n        $result = validate_ai_json($aiOutput);\n\n        if (!$result-\u003ejsonValid) {\n            throw new \\RuntimeException(\n                sprintf('AI JSON validation failed: %s',\n                    implode(', ', array_map(fn($e) =\u003e $e-\u003emessage, $result-\u003eerrors))\n                )\n            );\n        }\n\n        return $result-\u003edata;\n    }\n}\n```\n\n**Configuration in services.yaml:**\n```yaml\nservices:\n    App\\Service\\AiJsonProcessor:\n        arguments:\n            $apiKey: '%env(ANTHROPIC_API_KEY)%'\n```\n\n### With Streaming Responses\n\n```php\n\u003c?php\nrequire_once 'ai_json_cleanroom.php';\n\nfunction processStreamingResponse(string $apiUrl, array $headers, array $payload): array\n{\n    // Initialize streaming request\n    $ch = curl_init($apiUrl);\n    curl_setopt_array($ch, [\n        CURLOPT_POST =\u003e true,\n        CURLOPT_POSTFIELDS =\u003e json_encode($payload),\n        CURLOPT_HTTPHEADER =\u003e $headers,\n        CURLOPT_RETURNTRANSFER =\u003e true,\n        CURLOPT_WRITEFUNCTION =\u003e function($curl, $data) use (\u0026$chunks) {\n            $chunks[] = $data;\n            return strlen($data);\n        }\n    ]);\n\n    // Collect all chunks\n    $chunks = [];\n    curl_exec($ch);\n    curl_close($ch);\n\n    // Combine chunks\n    $fullOutput = implode('', $chunks);\n\n    // Validate complete output\n    $result = validate_ai_json($fullOutput);\n\n    if ($result-\u003elikelyTruncated) {\n        // Stream was truncated - reasons available\n        error_log('Stream truncated: ' . json_encode($result-\u003eerrors[0]-\u003edetail['truncation_reasons']));\n        throw new RuntimeException('Response was truncated, consider retrying with higher limits');\n    }\n\n    if (!$result-\u003ejsonValid) {\n        throw new RuntimeException('Failed to parse streamed JSON');\n    }\n\n    return $result-\u003edata;\n}\n```\n\n### With Guzzle Async/Promises\n\n```php\n\u003c?php\nrequire_once 'ai_json_cleanroom.php';\nuse GuzzleHttp\\Client;\nuse GuzzleHttp\\Promise;\n\nfunction processMultipleAiRequests(array $prompts): array\n{\n    $client = new Client();\n    $promises = [];\n\n    // Create async requests\n    foreach ($prompts as $key =\u003e $prompt) {\n        $promises[$key] = $client-\u003epostAsync('https://api.openai.com/v1/chat/completions', [\n            'headers' =\u003e [\n                'Authorization' =\u003e 'Bearer ' . getenv('OPENAI_API_KEY'),\n            ],\n            'json' =\u003e [\n                'model' =\u003e 'gpt-5.1',\n                'messages' =\u003e [['role' =\u003e 'user', 'content' =\u003e $prompt]]\n            ]\n        ]);\n    }\n\n    // Wait for all responses\n    $responses = Promise\\Utils::settle($promises)-\u003ewait();\n\n    $results = [];\n    foreach ($responses as $key =\u003e $response) {\n        if ($response['state'] === 'fulfilled') {\n            $data = json_decode($response['value']-\u003egetBody(), true);\n            $aiOutput = $data['choices'][0]['message']['content'];\n\n            // Validate each response\n            $result = validate_ai_json($aiOutput);\n\n            if ($result-\u003ejsonValid) {\n                $results[$key] = $result-\u003edata;\n            } else {\n                $results[$key] = [\n                    'error' =\u003e 'Validation failed',\n                    'details' =\u003e array_map(fn($e) =\u003e $e-\u003emessage, $result-\u003eerrors)\n                ];\n            }\n        } else {\n            $results[$key] = ['error' =\u003e 'Request failed'];\n        }\n    }\n\n    return $results;\n}\n\n// Usage\n$prompts = [\n    'user1' =\u003e 'Generate profile for Alice',\n    'user2' =\u003e 'Generate profile for Bob',\n    'user3' =\u003e 'Generate profile for Charlie',\n];\n\n$results = processMultipleAiRequests($prompts);\nprint_r($results);\n```\n\n---\n\n## API Reference\n\n### `validate_ai_json()`\n\nMain validation function with comprehensive options.\n\n```php\nfunction validate_ai_json(\n    string|array $inputData,\n    ?array $schema = null,\n    ?array $expectations = null,\n    ?ValidateOptions $options = null\n): ValidationResult\n```\n\n**Parameters:**\n- `$inputData`: String or already-parsed array\n- `$schema`: JSON Schema subset for validation\n- `$expectations`: List of path-based validation rules\n- `$options`: Configuration for parsing, extraction, and repair\n\n**Returns:** `ValidationResult` with `jsonValid`, `errors`, `warnings`, `data`, and `info`\n\n### `ValidationResult`\n\nResult object returned by `validate_ai_json()`.\n\n```php\nclass ValidationResult\n{\n    public bool $jsonValid;              // True if parsing and validation succeeded\n    public bool $likelyTruncated;        // True if input appears truncated\n    public array $errors;                // ValidationIssue[] - validation errors\n    public array $warnings;              // ValidationIssue[] - non-blocking warnings\n    public mixed $data;                  // Parsed JSON if valid, else null\n    public array $info;                  // Extraction/parsing metadata\n\n    public function toArray(): array;    // Convert result to associative array\n}\n```\n\n**Metadata in `$info`:**\n- `source`: How JSON was found (`\"raw\"`, `\"code_fence\"`, `\"balanced_block\"`, `\"object\"`)\n- `extraction`: Details about extraction process\n- `parse_backend`: Parser used (`\"json\"`)\n- `curly_quotes_normalization_used`: Whether typographic quotes were normalized\n- `repair`: Details about applied repairs (if any)\n\n### `ValidationIssue`\n\nIndividual validation error or warning.\n\n```php\nclass ValidationIssue\n{\n    public ErrorCode $code;              // Error type (enum)\n    public string $path;                 // JSONPath where error occurred\n    public string $message;              // Human-readable description\n    public ?array $detail;               // Additional context\n\n    public function toArray(): array;    // Convert issue to associative array\n}\n```\n\n### `ValidateOptions`\n\nConfiguration for validation behavior.\n\n```php\nclass ValidateOptions\n{\n    // Extraction options\n    public bool $strict = false;\n    public bool $extractJson = true;\n    public bool $allowJsonInCodeFences = true;\n    public bool $allowBareTopLevelScalars = false;\n    public bool $tolerateTrailingCommas = true;\n    public bool $stopOnFirstError = false;\n\n    // Repair options\n    public bool $enableSafeRepairs = true;\n    public bool $allowJson5Like = true;         // Master toggle for JSON5-like repairs\n    public bool $replaceConstants = true;        // True/False/None → true/false/null\n    public bool $replaceNansInfinities = true;   // NaN/Infinity → null\n    public int $maxTotalRepairs = 200;\n    public float $maxRepairsPercent = 0.02;      // 2% of input size\n\n    // Granular repair control\n    public string $normalizeCurlyQuotes = \"always\";  // \"always\"|\"auto\"|\"never\"\n    public bool $fixSingleQuotes = true;\n    public bool $quoteUnquotedKeys = true;\n    public bool $stripJsComments = true;\n\n    // Custom repair hooks\n    public ?array $customRepairHooks = null;\n}\n```\n\n**Curly quotes normalization modes:**\n- `\"always\"` (default): Normalize typographic quotes before parsing\n- `\"auto\"`: Try parsing first; only normalize if parse fails\n- `\"never\"`: Never normalize (preserves typographic quotes as-is)\n\n### `ErrorCode`\n\nEnumeration of validation error types.\n\n```php\nenum ErrorCode: string\n{\n    case PARSE_ERROR = 'parse_error';\n    case TRUNCATED = 'truncated';\n    case MISSING_REQUIRED = 'missing_required';\n    case TYPE_MISMATCH = 'type_mismatch';\n    case ENUM_MISMATCH = 'enum_mismatch';\n    case CONST_MISMATCH = 'const_mismatch';\n    case NOT_ALLOWED_EMPTY = 'not_allowed_empty';\n    case ADDITIONAL_PROPERTY = 'additional_property';\n    case PATTERN_MISMATCH = 'pattern_mismatch';\n    case MIN_LENGTH = 'min_length';\n    case MAX_LENGTH = 'max_length';\n    case MIN_ITEMS = 'min_items';\n    case MAX_ITEMS = 'max_items';\n    case MINIMUM = 'minimum';\n    case MAXIMUM = 'maximum';\n    case REPAIRED = 'repaired';  // Warning: repair was applied\n    // ... and more\n}\n```\n\n---\n\n## PHP-Specific Notes\n\n### Differences from Python Version\n\n1. **JSON Engine**: PHP uses native `json_decode()`/`json_encode()`. Unlike the Python version which can optionally use orjson for performance, PHP relies on its built-in JSON extension which is fast and reliable.\n2. **Type System**: PHP 8.1+ enums and typed properties used throughout\n3. **Arrays**: PHP associative arrays instead of Python dicts\n4. **Namespace**: Functions are global (no module imports needed)\n5. **Error Handling**: Non-throwing design (no exceptions from validate_ai_json)\n6. **Regex Patterns**: PHP regex requires delimiters (e.g., `'/pattern/'` not `'pattern'`)\n\n### UTF-8 Handling\n\nThis library requires **ext-mbstring** for proper UTF-8 multibyte character handling. All string operations use multibyte-safe functions (`mb_strlen()`, `mb_substr()`, `mb_str_split()`).\n\n**Why mbstring is required:**\n- Proper character counting for repair limits\n- Correct string slicing in multibyte contexts\n- Safe handling of emojis and international characters\n- Prevention of string corruption during repairs\n\n### Performance\n\nPHP's native JSON parser (`ext-json`) is highly optimized and written in C. Performance characteristics:\n\n#### Typical Processing Times\n\n| Scenario | Time | Notes |\n|----------|------|-------|\n| Clean JSON (no repairs) | ~0.1-1ms | Direct `json_decode()` |\n| Simple extraction + parse | ~1-2ms | From markdown code fence |\n| Multiple repairs + parse | ~2-5ms | Fix quotes, constants, comments |\n| Complex schema validation | ~5-20ms | Deep nested structure validation |\n| Large payload (\u003e100KB) | ~10-50ms | Depends on complexity |\n\n#### Performance Optimization Tips\n\n1. **Enable OPcache** (PHP's bytecode cache):\n   ```ini\n   ; In php.ini\n   opcache.enable=1\n   opcache.memory_consumption=128\n   opcache.interned_strings_buffer=8\n   opcache.max_accelerated_files=4000\n   ```\n\n2. **Disable unnecessary repairs:**\n   ```php\n   $options = new ValidateOptions();\n   $options-\u003estripJsComments = false;  // If you never have comments\n   $options-\u003enormalizeCurlyQuotes = 'never';  // If you never have smart quotes\n   ```\n\n3. **Use schema validation selectively:**\n   - Schema validation adds overhead proportional to complexity\n   - For simple checks, use path expectations instead\n   - Only validate what you actually need\n\n4. **For high-throughput scenarios:**\n   ```php\n   // Cache the ValidateOptions instance\n   static $options = null;\n   if ($options === null) {\n       $options = new ValidateOptions([\n           'maxTotalRepairs' =\u003e 100,  // Lower limit for faster processing\n           'stopOnFirstError' =\u003e true  // Fail fast\n       ]);\n   }\n\n   $result = validate_ai_json($input, options: $options);\n   ```\n\n#### Memory Usage\n\nMemory consumption is proportional to input size:\n- Small payloads (\u003c10KB): ~100-500KB peak memory\n- Medium payloads (10-100KB): ~500KB-2MB peak memory\n- Large payloads (\u003e100KB): ~2-10MB peak memory\n\nThe library processes inputs in a single pass where possible to minimize memory overhead.\n\n#### Comparison with Python Version\n\nWhile the Python version can use orjson for ~3-4x faster JSON parsing, PHP's native `json_decode()` is already quite fast (comparable to Python's stdlib json). The difference is negligible for most use cases (microseconds for typical AI outputs).\n\n---\n\n## Examples\n\nSee the [examples/](examples/) directory for complete, runnable examples:\n\n- **`basic_usage.php`** - Core features demonstration\n- **`openai_integration.php`** - OpenAI API integration\n- **`anthropic_claude.php`** - Anthropic Claude integration\n- **`streaming_responses.php`** - Handling streaming outputs\n- **`retry_logic_advanced.php`** - Intelligent retry strategies\n- **`custom_repair_hooks.php`** - Domain-specific repairs\n\nRun any example:\n```bash\nphp examples/basic_usage.php\n```\n\n---\n\n## Should I Use This Tool?\n\n### Quick Decision Guide\n\n**Use AI JSON Cleanroom if you:**\n- Work with any AI model (GPT, Claude, Gemini, Llama)\n- Receive JSON wrapped in explanations or markdown\n- Face token limit truncations\n- Need detailed error messages for retries\n- Want one solution for all AI quirks\n- Value zero dependencies (stdlib only)\n- Use Laravel, Symfony, or vanilla PHP\n\n**You might not need it if you:**\n- Only work with clean, guaranteed JSON\n- Control token generation completely\n- Never hit token limits\n- Your AI model never adds explanatory text\n- You have a custom parsing pipeline that already works\n\n### Comparison with Common Approaches\n\n**Your Current Approach** → **With Cleanroom**\n\n| Without Cleanroom | With Cleanroom |\n|-------------------|----------------|\n| `try { json_decode(); }` | Always get a result, never crashes |\n| Regex extraction | Automatic markdown/fence detection |\n| Custom retry logic | Structured errors for targeted retries |\n| \"Is it truncated?\" | Immediate truncation detection with reasons |\n| Multiple fix attempts | One call handles everything |\n| Scattered error handling | Unified validation pipeline |\n\n### Real-World Use Cases\n\n#### Use Case 1: AI-Powered SaaS Application\n```php\n// Before: Fragile and unreliable\ntry {\n    $data = json_decode($aiOutput, true);\n    if (json_last_error() !== JSON_ERROR_NONE) {\n        // Retry? Log? Give up? ¯\\_(ツ)_/¯\n    }\n} catch (Exception $e) {\n    // Something went wrong...\n}\n\n// After: Robust and informative\n$result = validate_ai_json($aiOutput, schema: $userSchema);\nif ($result-\u003ejsonValid) {\n    return $result-\u003edata;  // ✅ Clean, validated data\n} elseif ($result-\u003elikelyTruncated) {\n    return retryWithHigherTokens();  // ✅ Know exactly what to do\n} else {\n    return buildRetryPrompt($result-\u003eerrors);  // ✅ Targeted fixes\n}\n```\n\n#### Use Case 2: Laravel API Endpoint\n```php\n// Clean AI responses reliably in your Laravel services\nclass AiService {\n    public function getStructuredData(string $prompt): array {\n        $aiResponse = $this-\u003ecallAiApi($prompt);\n        $result = validate_ai_json($aiResponse);\n\n        if (!$result-\u003ejsonValid) {\n            Log::warning('AI JSON validation failed', [\n                'errors' =\u003e $result-\u003eerrors,\n                'truncated' =\u003e $result-\u003elikelyTruncated\n            ]);\n            throw new AiResponseException('Invalid response');\n        }\n\n        return $result-\u003edata;\n    }\n}\n```\n\n#### Use Case 3: Batch Processing\n```php\n// Process hundreds of AI outputs reliably\nforeach ($aiOutputs as $output) {\n    $result = validate_ai_json($output);\n\n    if ($result-\u003ejsonValid) {\n        $processed[] = $result-\u003edata;\n    } elseif ($result-\u003elikelyTruncated) {\n        $needsRetry[] = $output;\n    } else {\n        $failed[] = [\n            'output' =\u003e $output,\n            'errors' =\u003e $result-\u003eerrors\n        ];\n    }\n}\n```\n\n### The Bottom Line\n\nIf you've ever written code like this:\n\n```php\n// This is a common scenario...\ntry {\n    $data = json_decode($aiOutput, true);\n} catch (Exception $e) {\n    // Try to extract JSON with regex\n    preg_match('/\\{.*\\}/s', $aiOutput, $matches);\n    if ($matches) {\n        try {\n            // Fix quotes maybe?\n            $fixed = str_replace(\"'\", '\"', $matches[0]);\n            $data = json_decode($fixed, true);\n        } catch (Exception $e2) {\n            // Give up\n            throw new RuntimeException(\"Can't parse AI output\");\n        }\n    }\n}\n```\n\nThen yes, you need this tool. It handles all of that (and much more) in one line:\n\n```php\n$result = validate_ai_json($aiOutput);  // Done.\n```\n\n**Benefits:**\n- ✅ No more silent failures\n- ✅ No more guessing why parsing failed\n- ✅ No more wasted API calls on truncated responses\n- ✅ No more fragile regex patterns\n- ✅ No more scattered error handling\n\n---\n\n## Testing\n\nThis library includes a comprehensive PHPUnit test suite.\n\n### Run Tests\n\n```bash\n# Install dependencies\ncomposer install\n\n# Run all tests\ncomposer test\n\n# Run with coverage (requires Xdebug)\ncomposer test-coverage\n\n# Run specific test\n./vendor/bin/phpunit tests/ExtractionTest.php\n```\n\nSee [tests/README.md](tests/README.md) for detailed testing documentation.\n\n---\n\n## License\n\nMIT License\n\nCopyright (c) 2025 Jordi Cor\n\nSee [LICENSE](LICENSE) file for details.\n\n---\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n---\n\n## Support\n\n- **Issues**: [GitHub Issues](https://github.com/jordicor/ai-json-cleanroom-php/issues)\n- **Source**: [GitHub Repository](https://github.com/jordicor/ai-json-cleanroom-php)\n- **Python Version**: [Original Project](https://github.com/jordicor/ai-json-cleanroom)\n\n---\n\nIf you find this tool useful, please consider starring the repo! ⭐\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjordicor%2Fai-json-cleanroom-php","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjordicor%2Fai-json-cleanroom-php","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjordicor%2Fai-json-cleanroom-php/lists"}