What's Actually Happening

Your PHP application fails to decode JSON data, returning null or throwing errors when attempting to parse JSON strings from API responses, user input, file contents, or database storage. The JSON may be malformed with syntax errors, contain invalid characters, have encoding issues, or include structures that don't conform to expected formats. Without proper error handling, your application silently fails, returns unexpected null values, or crashes when attempting to access properties on null decoded results.

JSON decode failures commonly occur when receiving data from external APIs with inconsistent formatting, processing user-submitted JSON with mistakes, reading corrupted JSON files, handling data with special characters that weren't properly escaped, or working with different encoding standards between systems. The lack of error feedback from json_decode() makes debugging challenging.

The Error You'll See

``` Warning: json_decode(): JSON decode error in /var/www/html/api/handler.php on line 45

Warning: json_decode(): Maximum stack depth exceeded in /var/www/app/parser.php on line 128

Warning: json_decode(): Syntax error in /var/www/api/import.php on line 67

Warning: json_decode(): Unexpected character in /var/www/services/JsonService.php:89

Warning: json_decode(): Malformed UTF-8 characters, possibly incorrectly encoded

Fatal error: Uncaught TypeError: Argument 1 passed to process() must be of the type array, null given

PHP Warning: json_decode(): Control character error, possibly incorrectly encoded in /var/www/html/process.php on line 23

PHP Warning: json_decode(): State mismatch (invalid or malformed JSON) in /home/user/app.php:156

[08-Apr-2026 16:30:12 UTC] PHP Warning: json_decode(): JSON decode error: Syntax error, malformed JSON in /var/www/api/dispatcher.php on line 89 ```

When json_decode returns null but you expect data:

``` $result = json_decode($jsonString); // $result is null - no data but also no explicit error

// Later in code $name = $result->name; // Fatal error: Attempt to read property "name" on null

// Or with array access $items = json_decode($jsonString, true); $firstItem = $items[0]; // Warning: Undefined array key 0 (null treated as array) ```

Why This Happens

  1. 1.Syntax errors in JSON: Missing quotes, trailing commas, unquoted keys, single quotes instead of double quotes, missing closing braces/brackets, or extra characters outside the JSON structure. JSON syntax is strict and differs from JavaScript object syntax.
  2. 2.Malformed UTF-8 encoding: Invalid UTF-8 byte sequences, mixed encodings, or characters that weren't properly encoded before JSON serialization. PHP's json_decode expects valid UTF-8.
  3. 3.Control characters: Unescaped control characters (ASCII 0-31) in strings that should be escaped according to JSON specification. Tabs, newlines, and other control characters must use escape sequences.
  4. 4.Maximum depth exceeded: Deeply nested JSON structures exceed PHP's default stack depth limit (default 512 levels). This can happen with complex nested objects or recursive data structures.
  5. 5.Invalid numeric formats: Leading zeros on integers, overly large numbers beyond PHP's numeric limits, infinity or NaN values that aren't valid in JSON.
  6. 6.Empty or null input: Passing an empty string, null value, or completely non-JSON content to json_decode which expects valid JSON text.
  7. 7.BOM or whitespace issues: UTF-8 BOM (Byte Order Mark) at the beginning, extra whitespace, or invisible characters that prevent proper parsing.
  8. 8.Incorrect escaping: Backslashes not properly doubled, Unicode escapes with invalid formats, or HTML/script content with special characters.
  9. 9.Mixed PHP serialization: Data that was PHP-serialized and mistaken for JSON, or JSON containing serialized PHP objects.
  10. 10.API response inconsistencies: External APIs returning errors in different formats, HTML error pages instead of JSON, or rate-limiting messages in non-JSON format.

Step 1: Validate JSON Before Decoding

Always validate JSON strings before decoding:

```php <?php // Check for JSON validity before decoding function isValidJson($jsonString) { if (!is_string($jsonString) || trim($jsonString) === '') { return false; }

json_decode($jsonString); return json_last_error() === JSON_ERROR_NONE; }

// Check with detail function validateJson($jsonString) { if (!is_string($jsonString)) { return ['valid' => false, 'error' => 'Input is not a string']; }

if (trim($jsonString) === '') { return ['valid' => false, 'error' => 'Empty string']; }

json_decode($jsonString); $error = json_last_error();

if ($error === JSON_ERROR_NONE) { return ['valid' => true, 'error' => null]; }

return [ 'valid' => false, 'error' => json_last_error_msg(), 'error_code' => $error ]; }

// Usage $json = '{"name":"John","age":30}'; $result = validateJson($json);

if ($result['valid']) { $data = json_decode($json, true); // Process data } else { echo "Invalid JSON: " . $result['error']; }

// Robust JSON handling function safeJsonDecode($jsonString, $assoc = false, $depth = 512, $options = 0) { // Validate input if (!is_string($jsonString) || $jsonString === '') { throw new InvalidArgumentException('Input must be a non-empty string'); }

// Trim whitespace and BOM $jsonString = trim($jsonString); if (substr($jsonString, 0, 3) === "\xEF\xBB\xBF") { $jsonString = substr($jsonString, 3); // Remove UTF-8 BOM }

// Attempt decode $data = json_decode($jsonString, $assoc, $depth, $options);

if ($data === null && json_last_error() !== JSON_ERROR_NONE) { throw new JsonException(json_last_error_msg(), json_last_error()); }

return $data; }

// Usage with exception handling try { $data = safeJsonDecode($jsonString, true); processData($data); } catch (JsonException $e) { error_log("JSON decode failed: " . $e->getMessage()); // Handle error gracefully } ?> ```

Step 2: Handle json_decode Errors Properly

Use json_last_error() and json_last_error_msg() for error detection:

```php <?php // Basic error checking $jsonString = '{"name":"John",}'; // Trailing comma - invalid JSON

$data = json_decode($jsonString, true);

if ($data === null && json_last_error() !== JSON_ERROR_NONE) { echo "JSON decode error: " . json_last_error_msg(); }

// Detailed error handling function decodeJsonWithErrorHandling($jsonString) { $data = json_decode($jsonString, true);

if (json_last_error() !== JSON_ERROR_NONE) { $errorDetails = [ 'error_code' => json_last_error(), 'error_message' => json_last_error_msg(), 'json_snippet' => substr($jsonString, 0, 100), 'json_length' => strlen($jsonString), ];

switch (json_last_error()) { case JSON_ERROR_DEPTH: $errorDetails['suggestion'] = 'JSON nested too deeply. Increase depth limit.'; break; case JSON_ERROR_STATE_MISMATCH: $errorDetails['suggestion'] = 'Invalid or malformed JSON structure.'; break; case JSON_ERROR_CTRL_CHAR: $errorDetails['suggestion'] = 'Unexpected control character found. Check for unescaped tabs/newlines.'; break; case JSON_ERROR_SYNTAX: $errorDetails['suggestion'] = 'Syntax error. Check quotes, commas, brackets.'; break; case JSON_ERROR_UTF8: $errorDetails['suggestion'] = 'Malformed UTF-8 characters. Check encoding.'; break; }

return [ 'success' => false, 'error' => $errorDetails ]; }

return [ 'success' => true, 'data' => $data ]; }

// PHP 7.3+: Use JSON_THROW_ON_ERROR option try { $data = json_decode($jsonString, true, 512, JSON_THROW_ON_ERROR); // Process $data } catch (JsonException $e) { error_log("JSON error: " . $e->getMessage() . " at line " . $e->getLine());

// Handle based on error type if ($e->getCode() === JSON_ERROR_SYNTAX) { // Syntax error - possibly malformed input $cleanJson = attemptJsonCleanup($jsonString); $data = json_decode($cleanJson, true); } }

// Error codes reference function getJsonErrorDescription($errorCode) { $errors = [ JSON_ERROR_NONE => 'No error', JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', JSON_ERROR_STATE_MISMATCH => 'Underflow or the modes mismatch', JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON', JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded', JSON_ERROR_RECURSION => 'One or more recursive references in the value to be encoded', JSON_ERROR_INF_OR_NAN => 'One or more NAN or INF values in the value to be encoded', JSON_ERROR_UNSUPPORTED_TYPE => 'A value of a type that cannot be encoded was given', ];

return $errors[$errorCode] ?? 'Unknown error'; } ?> ```

Step 3: Fix Common JSON Syntax Errors

Identify and fix common syntax issues:

```php <?php // Common JSON syntax errors and fixes

// ERROR 1: Trailing commas (invalid in JSON, valid in JavaScript) // Invalid: {"name":"John","age":30,} // Valid: {"name":"John","age":30} function removeTrailingCommas($jsonString) { // Remove trailing commas before closing braces/brackets return preg_replace('/,\s*([}]])/', '$1', $jsonString); }

// ERROR 2: Single quotes instead of double quotes // Invalid: {'name':'John'} // Valid: {"name":"John"} function convertQuotes($jsonString) { // This is tricky - needs careful handling // Simple replacement may break valid JSON with single quotes in strings return preg_replace('/\'([^\']+)\'(?=:)/', '"$1"', $jsonString); }

// ERROR 3: Unquoted keys // Invalid: {name:"John"} // Valid: {"name":"John"} function quoteKeys($jsonString) { return preg_replace('/([{,]\s*)([a-zA-Z_][a-zA-Z0-9_]*)(\s*:)/', '$1"$2"$3', $jsonString); }

// ERROR 4: Missing quotes on string values // Invalid: {"status":active} // Valid: {"status":"active"} // Complex - would need parsing to determine if value is string vs number/boolean

// ERROR 5: Extra characters outside JSON // Invalid: {"data":"value"} some extra text // Valid: {"data":"value"} function extractJson($content) { // Find JSON object or array boundaries if (preg_match('/(\{.*\}|[.*])/s', $content, $match)) { return $match[0]; }

// Try to find first { or [ and extract until balanced $startChar = null; $startPos = null;

for ($i = 0; $i < strlen($content); $i++) { if ($content[$i] === '{' || $content[$i] === '[') { $startChar = $content[$i]; $startPos = $i; break; } }

if ($startPos === null) { return null; }

$endChar = $startChar === '{' ? '}' : ']'; $depth = 0;

for ($i = $startPos; $i < strlen($content); $i++) { if ($content[$i] === $startChar) $depth++; if ($content[$i] === $endChar) $depth--;

if ($depth === 0) { return substr($content, $startPos, $i - $startPos + 1); } }

return null; }

// ERROR 6: Comments (invalid in JSON) // Invalid: {"name":"John" /* comment */} // Valid: {"name":"John"} function removeComments($jsonString) { // Remove /* */ comments $jsonString = preg_replace('/\/*.*?*\//s', '', $jsonString); // Remove // comments (be careful not to remove URLs) $jsonString = preg_replace('/\/\/[^\n]*(?=\n)/', '', $jsonString); return $jsonString; }

// Comprehensive cleanup function function cleanupJson($jsonString) { // Remove BOM if (substr($jsonString, 0, 3) === "\xEF\xBB\xBF") { $jsonString = substr($jsonString, 3); }

// Trim whitespace $jsonString = trim($jsonString);

// Extract JSON if embedded in other content $jsonString = extractJson($jsonString) ?? $jsonString;

// Remove comments $jsonString = removeComments($jsonString);

// Remove trailing commas $jsonString = removeTrailingCommas($jsonString);

return $jsonString; }

// Apply cleanup and decode function decodeDirtyJson($jsonString) { $cleanJson = cleanupJson($jsonString);

$data = json_decode($cleanJson, true);

if ($data !== null) { return $data; }

// Try more aggressive fixes $cleanJson = quoteKeys($cleanJson); $cleanJson = preg_replace('/\'([^\']+)\'/', '"$1"', $cleanJson);

return json_decode($cleanJson, true); } ?> ```

Step 4: Handle Encoding and UTF-8 Issues

Fix character encoding problems:

```php <?php // Check string encoding function ensureUtf8($string) { if (!mb_check_encoding($string, 'UTF-8')) { // Try to convert $string = mb_convert_encoding($string, 'UTF-8', 'auto'); }

return $string; }

// Remove invalid UTF-8 sequences function sanitizeUtf8($string) { // Remove invalid UTF-8 characters $string = mb_convert_encoding($string, 'UTF-8', 'UTF-8');

// Alternative: filter out invalid sequences return iconv('UTF-8', 'UTF-8//IGNORE', $string); }

// Detect and fix encoding before decode function decodeJsonWithEncodingCheck($jsonString) { // Check encoding if (!mb_check_encoding($jsonString, 'UTF-8')) { // Try to detect encoding $encoding = mb_detect_encoding($jsonString, ['UTF-8', 'ISO-8859-1', 'Windows-1252'], true);

if ($encoding && $encoding !== 'UTF-8') { $jsonString = mb_convert_encoding($jsonString, 'UTF-8', $encoding); } else { // Force UTF-8, removing invalid sequences $jsonString = iconv('UTF-8', 'UTF-8//IGNORE', $jsonString); } }

return json_decode($jsonString, true); }

// Fix control characters (ASCII 0-31) function removeControlChars($jsonString) { // JSON allows these: \b, \f, \n, \r, \t // Others should be escaped or removed

$result = ''; $len = strlen($jsonString);

for ($i = 0; $i < $len; $i++) { $char = $jsonString[$i]; $ord = ord($char);

// Keep printable chars, whitespace (9,10,13,32+), and proper escapes if ($ord >= 32 || $ord === 9 || $ord === 10 || $ord === 13) { $result .= $char; } elseif ($char === '\\') { // Keep escape sequences intact $result .= $char; if ($i + 1 < $len) { $result .= $jsonString[++$i]; } } // Skip other control characters }

return $result; }

// Properly encode strings for JSON function jsonEncodeValue($value) { if (is_string($value)) { // Ensure UTF-8 if (!mb_check_encoding($value, 'UTF-8')) { $value = mb_convert_encoding($value, 'UTF-8', 'auto'); }

// Escape for JSON return json_encode($value); }

return json_encode($value); }

// Handle large integers that PHP can't represent function decodeWithBigIntSupport($jsonString) { // Use JSON_BIGINT_AS_STRING option $data = json_decode( $jsonString, true, 512, JSON_BIGINT_AS_STRING );

return $data; }

// Handle Unicode escape sequences function decodeUnicodeEscapes($jsonString) { // JSON uses \uXXXX for Unicode // This is handled automatically by json_decode if valid

// If you see literal \u sequences not properly escaped $jsonString = preg_replace('/\\\\u([0-9a-fA-F]{4})/', '\\u$1', $jsonString);

return $jsonString; } ?> ```

Step 5: Handle API Response Errors

Robust API JSON handling:

```php <?php class ApiResponseHandler { public function parse($response) { // Check if response is actually JSON if (!$this->looksLikeJson($response)) { // Might be an error page, rate limit message, etc. throw new ApiException('Response is not valid JSON format'); }

// Clean up response $response = $this->cleanResponse($response);

// Decode with error handling try { $data = json_decode($response, true, 512, JSON_THROW_ON_ERROR); } catch (JsonException $e) { throw new ApiException('Failed to parse API response: ' . $e->getMessage()); }

// Validate expected structure $this->validateStructure($data);

return $data; }

private function looksLikeJson($string) { if (!is_string($string)) { return false; }

$trimmed = trim($string);

// Starts with { or [ if ($trimmed[0] === '{' || $trimmed[0] === '[') { return true; }

// Check Content-Type header if available return false; }

private function cleanResponse($response) { // Remove BOM if (substr($response, 0, 3) === "\xEF\xBB\xBF") { $response = substr($response, 3); }

// Trim $response = trim($response);

return $response; }

private function validateStructure($data) { // Check for API error response if (isset($data['error']) || isset($data['errors'])) { $message = $data['error'] ?? json_encode($data['errors']); throw new ApiException('API returned error: ' . $message); }

// Check for expected fields if (!isset($data['success']) || !$data['success']) { throw new ApiException('API response indicates failure'); } } }

// Fetch and parse API response function fetchApiJson($url, $options = []) { $context = stream_context_create([ 'http' => [ 'method' => $options['method'] ?? 'GET', 'header' => $options['headers'] ?? [], 'timeout' => $options['timeout'] ?? 30, ] ]);

$response = @file_get_contents($url, false, $context);

if ($response === false) { $error = error_get_last(); throw new ApiException('Failed to fetch API: ' . $error['message']); }

// Check HTTP response headers for Content-Type $contentType = $options['expected_content_type'] ?? 'application/json';

// Parse response $handler = new ApiResponseHandler();

try { return $handler->parse($response); } catch (ApiException $e) { // Log full response for debugging error_log('API response parse failed. Response: ' . substr($response, 0, 500)); throw $e; } }

// Handle cURL responses function fetchApiJsonWithCurl($url) { $ch = curl_init($url);

curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_TIMEOUT => 30, CURLOPT_HEADER => false, ]);

$response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE); $error = curl_error($ch);

curl_close($ch);

if ($error) { throw new ApiException('cURL error: ' . $error); }

if ($httpCode >= 400) { throw new ApiException('HTTP error: ' . $httpCode); }

if (!strpos($contentType, 'application/json') !== false) { // Content-Type is not JSON throw new ApiException('Unexpected content type: ' . $contentType); }

return json_decode($response, true, 512, JSON_THROW_ON_ERROR); }

// Graceful fallback for API failures function safeApiCall($url, $fallbackValue = null) { try { return fetchApiJson($url); } catch (ApiException $e) { error_log('API call failed: ' . $e->getMessage()); return $fallbackValue; } } ?> ```

Step 6: Implement JSON Streaming for Large Files

Handle large JSON files efficiently:

```php <?php // For large JSON, don't load entire file into memory // Use streaming parser

// Option 1: Process file line by line for JSON lines format function processJsonLines($file) { $handle = fopen($file, 'r');

while (($line = fgets($handle)) !== false) { $line = trim($line);

if ($line === '') continue;

$data = json_decode($line, true);

if ($data !== null) { processRecord($data); } else { error_log('Failed to decode line: ' . substr($line, 0, 100)); } }

fclose($handle); }

// Option 2: Parse JSON incrementally function parseLargeJsonIncrementally($file) { $handle = fopen($file, 'r'); $buffer = ''; $depth = 0; $inString = false; $escape = false;

while (!feof($handle)) { $char = fread($handle, 1);

if ($escape) { $buffer .= $char; $escape = false; continue; }

if ($char === '\\') { $buffer .= $char; $escape = true; continue; }

if ($char === '"' && !$escape) { $inString = !$inString; $buffer .= $char; continue; }

if (!$inString) { if ($char === '{' || $char === '[') { $depth++; } elseif ($char === '}' || $char === ']') { $depth--; } }

$buffer .= $char;

// When depth returns to 0, we have complete JSON if ($depth === 0 && trim($buffer) !== '') { $data = json_decode($buffer, true);

if ($data !== null) { processData($data); }

$buffer = ''; }

// Prevent buffer from growing too large if (strlen($buffer) > 100000) { throw new RuntimeException('JSON parse buffer overflow'); } }

fclose($handle); }

// Option 3: Use external JSON streaming library // Install: composer require salanhe/json-streaming-parser function streamJsonWithParser($file) { require 'vendor/autoload.php';

$listener = new class implements JsonStreamingParser\Listener { public function filePosition($line, $char) {} public function startDocument() {} public function endDocument() {} public function startObject() {} public function endObject() {} public function startArray() {} public function endArray() {} public function key($key) {} public function value($value) { // Process each value as it's parsed processValue($value); } };

$parser = new JsonStreamingParser\Parser(fopen($file, 'r'), $listener); $parser->parse(); }

// Option 4: Chunked approach for array JSON function processLargeJsonArray($file, $chunkSize = 1000) { // Read entire file - not ideal for very large files // But works for moderately large arrays

$content = file_get_contents($file); $data = json_decode($content, true);

if (!is_array($data)) { throw new RuntimeException('Expected JSON array'); }

// Process in chunks foreach (array_chunk($data, $chunkSize) as $chunk) { foreach ($chunk as $item) { processItem($item); }

// Free memory after each chunk gc_collect_cycles(); } }

// Memory-efficient JSON file writing function writeLargeJsonArray($filename, $generator) { $handle = fopen($filename, 'w');

fwrite($handle, '[');

$first = true;

foreach ($generator as $item) { if (!$first) { fwrite($handle, ','); }

fwrite($handle, json_encode($item)); $first = false; }

fwrite($handle, ']'); fclose($handle); }

// Stream JSON response to client function streamJsonResponse($data) { header('Content-Type: application/json');

if (!is_array($data)) { echo json_encode($data); return; }

echo '['; $first = true;

foreach ($data as $item) { if (!$first) { echo ','; }

echo json_encode($item); $first = false;

// Flush periodically if (ob_get_level() > 0) { ob_flush(); } flush(); }

echo ']'; } ?> ```

Step 7: Debug JSON Decode Failures

Create debugging tools for JSON issues:

```php <?php class JsonDebugger { private $jsonString;

public function __construct($jsonString) { $this->jsonString = $jsonString; }

public function diagnose() { $report = [];

// Basic checks $report['input_type'] = gettype($this->jsonString); $report['length'] = strlen($this->jsonString); $report['first_100_chars'] = substr($this->jsonString, 0, 100); $report['last_100_chars'] = substr($this->jsonString, -100);

// Encoding check $report['encoding'] = mb_detect_encoding($this->jsonString); $report['valid_utf8'] = mb_check_encoding($this->jsonString, 'UTF-8');

// BOM check $report['has_bom'] = substr($this->jsonString, 0, 3) === "\xEF\xBB\xBF";

// First character analysis $trimmed = trim($this->jsonString); $report['first_char'] = $trimmed[0] ?? null; $report['expected_structure'] = $trimmed[0] === '{' ? 'object' : ($trimmed[0] === '[' ? 'array' : 'unknown');

// Try decode json_decode($this->jsonString); $report['decode_error'] = json_last_error_msg(); $report['decode_error_code'] = json_last_error();

// Find specific issues $report['issues'] = $this->findIssues();

// Find error position (approximate) $report['error_position'] = $this->findErrorPosition();

return $report; }

private function findIssues() { $issues = [];

// Check for trailing commas if (preg_match('/,\s*[}]]/', $this->jsonString)) { $issues[] = 'Trailing comma detected'; }

// Check for single quotes on keys if (preg_match('/\'[^\']+\'\s*:/', $this->jsonString)) { $issues[] = 'Single quotes on keys (use double quotes)'; }

// Check for unquoted keys if (preg_match('/[{,]\s*[a-zA-Z_][a-zA-Z0-9_]*\s*:/', $this->jsonString)) { $issues[] = 'Unquoted keys detected'; }

// Check for comments if (preg_match('/\/*|\/\//', $this->jsonString)) { $issues[] = 'Comments detected (not valid in JSON)'; }

// Check for control characters if (preg_match('/[\x00-\x08\x0B\x0C\x0E-\x1F]/', $this->jsonString)) { $issues[] = 'Control characters detected'; }

// Check balance of brackets $openBraces = substr_count($this->jsonString, '{'); $closeBraces = substr_count($this->jsonString, '}'); $openBrackets = substr_count($this->jsonString, '['); $closeBrackets = substr_count($this->jsonString, ']');

if ($openBraces !== $closeBraces) { $issues[] = "Unbalanced braces: $openBraces open, $closeBraces close"; }

if ($openBrackets !== $closeBrackets) { $issues[] = "Unbalanced brackets: $openBrackets open, $closeBrackets close"; }

return $issues; }

private function findErrorPosition() { // Try to find where JSON parsing fails // Incrementally parse larger portions until error

for ($len = 10; $len <= strlen($this->jsonString); $len += 10) { $chunk = substr($this->jsonString, 0, $len); json_decode($chunk);

if (json_last_error() !== JSON_ERROR_NONE) { return [ 'position' => $len - 10, 'context' => substr($this->jsonString, max(0, $len - 30), 50) ]; } }

return null; }

public function printReport() { $report = $this->diagnose();

echo "=== JSON Debug Report ===\n";

foreach ($report as $key => $value) { if (is_array($value)) { echo "$key:\n"; foreach ($value as $v) { echo " - $v\n"; } } else { echo "$key: $value\n"; } } } }

// Usage $debugger = new JsonDebugger($malformedJson); $debugger->printReport();

// Quick diagnostic function function quickJsonDiagnose($json) { json_decode($json);

if (json_last_error() === JSON_ERROR_NONE) { echo "JSON is valid\n"; return true; }

echo "JSON Error: " . json_last_error_msg() . "\n";

// Show problematic area $debugger = new JsonDebugger($json); $pos = $debugger->findErrorPosition();

if ($pos) { echo "Error near position {$pos['position']}\n"; echo "Context: {$pos['context']}\n"; }

return false; } ?> ```

Step 8: Create Robust JSON Encoder

Ensure proper JSON encoding:

```php <?php // Safe JSON encoding with error handling function safeJsonEncode($data, $flags = 0) { $result = json_encode($data, $flags | JSON_THROW_ON_ERROR);

return $result; }

// Encode with UTF-8 validation function encodeValidJson($data) { // Recursively ensure UTF-8 encoding $data = ensureUtf8Recursive($data);

return json_encode($data, JSON_UNESCAPED_UNICODE); }

function ensureUtf8Recursive($data) { if (is_string($data)) { return mb_convert_encoding($data, 'UTF-8', 'auto'); }

if (is_array($data)) { return array_map('ensureUtf8Recursive', $data); }

if (is_object($data)) { $result = new stdClass(); foreach ($data as $key => $value) { $result->$key = ensureUtf8Recursive($value); } return $result; }

return $data; }

// Encode with pretty print for debugging function encodePrettyJson($data) { return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); }

// Encode for safe HTML output function encodeHtmlSafeJson($data) { // Escape characters that might cause issues in HTML $json = json_encode($data);

// Convert to HTML-safe return htmlspecialchars($json, ENT_QUOTES, 'UTF-8'); }

// Encode with custom escaping function encodeWithCustomEscaping($data) { $options = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP; return json_encode($data, $options); }

// Encode circular reference safe function encodeCircularSafe($data) { // PHP 5.4+ handles circular references with JSON_PARTIAL_OUTPUT_ON_ERROR return json_encode($data, JSON_PARTIAL_OUTPUT_ON_ERROR); }

// Debug why encoding failed function debugJsonEncode($data) { $result = json_encode($data);

if ($result === false) { $error = json_last_error();

switch ($error) { case JSON_ERROR_RECURSION: echo "Circular reference detected in data\n"; // Find circular references $circular = findCircularReferences($data); print_r($circular); break;

case JSON_ERROR_INF_OR_NAN: echo "INF or NAN values cannot be JSON encoded\n"; // Find non-numeric values $nonNumeric = findNonNumeric($data); print_r($nonNumeric); break;

case JSON_ERROR_UNSUPPORTED_TYPE: echo "Unsupported type in data (e.g., resource)\n"; // Find resources $resources = findResources($data); print_r($resources); break;

default: echo json_last_error_msg() . "\n"; }

return null; }

return $result; }

function findCircularReferences($data, &$seen = []) { $circular = [];

if (is_object($data)) { $hash = spl_object_hash($data);

if (isset($seen[$hash])) { return ['Circular at ' . $hash]; }

$seen[$hash] = true;

foreach ($data as $key => $value) { $result = findCircularReferences($value, $seen); if ($result) { $circular[$key] = $result; } } }

if (is_array($data)) { foreach ($data as $key => $value) { $result = findCircularReferences($value, $seen); if ($result) { $circular[$key] = $result; } } }

return $circular; }

function findNonNumeric($data, $path = '') { $found = [];

if (is_float($data) && (is_infinite($data) || is_nan($data))) { $found[$path] = $data; }

if (is_array($data) || is_object($data)) { foreach ($data as $key => $value) { $newPath = $path ? "$path.$key" : $key; $result = findNonNumeric($value, $newPath); $found = array_merge($found, $result); } }

return $found; }

function findResources($data, $path = '') { $found = [];

if (is_resource($data)) { $found[$path] = get_resource_type($data); }

if (is_array($data) || is_object($data)) { foreach ($data as $key => $value) { $newPath = $path ? "$path.$key" : $key; $result = findResources($value, $newPath); $found = array_merge($found, $result); } }

return $found; } ?> ```

Step 9: Handle Special JSON Cases

Address edge cases in JSON handling:

```php <?php // Handle JSON with large numbers (beyond PHP int range) function decodeLargeNumbers($json) { return json_decode($json, true, 512, JSON_BIGINT_AS_STRING); }

// Test JSON with very large integers $largeIntJson = '{"id": 9223372036854775807}'; $data = decodeLargeNumbers($largeIntJson); // $data['id'] will be string "9223372036854775807" instead of overflow

// Handle empty JSON function decodeEmptyJson($json) { if (trim($json) === '') { return null; }

if (trim($json) === '{}') { return []; }

if (trim($json) === '[]') { return []; }

return json_decode($json, true); }

// Handle JSON with null values function handleNullValues($data) { // json_decode('{"key":null}') returns ['key' => null] // This is valid JSON

if ($data === null) { // Entire JSON was null - "null" is valid JSON! return null; }

// Process null values in structure foreach ($data as $key => $value) { if ($value === null) { // Handle null appropriately $data[$key] = ''; // Or keep null, depending on needs } }

return $data; }

// Handle JSON booleans // json_decode('true') returns true // json_decode('false') returns false // These are valid JSON primitives!

function decodeJsonPrimitive($json) { $data = json_decode($json);

// Check for primitive values (true, false, null, numbers, strings) if (!is_array($data) && !is_object($data)) { return ['type' => 'primitive', 'value' => $data]; }

return ['type' => 'structure', 'value' => $data]; }

// Handle mixed content (JSON embedded in other text) function extractEmbeddedJson($content) { // Try to find JSON patterns $patterns = [ '/\{.*\}/s', // Object '/[.*]/s', // Array ];

foreach ($patterns as $pattern) { if (preg_match($pattern, $content, $match)) { $json = $match[0]; $data = json_decode($json, true);

if ($data !== null) { return $data; } } }

return null; }

// Handle escaped characters properly function unescapeJsonString($string) { // This is done automatically by json_decode // But if you need to manually unescape

$string = str_replace('\\n', '\n', $string); $string = str_replace('\\r', '\r', $string); $string = str_replace('\\t', '\t', $string); $string = str_replace('\\"', '"', $string); $string = str_replace('\\\\', '\\', $string);

return $string; }

// Handle JSON in database storage function jsonFromDatabase($jsonColumn) { // Some databases may store JSON with extra quotes or escaping $json = trim($jsonColumn);

// Remove extra outer quotes if present if (substr($json, 0, 1) === '"' && substr($json, -1) === '"') { $json = substr($json, 1, -1); }

// Unescape if double-escaped $json = stripslashes($json);

return json_decode($json, true); }

function jsonToDatabase($data) { // Ensure proper encoding for storage $json = json_encode($data, JSON_UNESCAPED_UNICODE);

// Some databases need escaping return addslashes($json); }

// Handle JSONP responses function parseJsonp($jsonpResponse, $callbackName = null) { // JSONP format: callbackName({"data":"value"})

// Find callback name if not provided if ($callbackName === null) { if (preg_match('/^([a-zA-Z_][a-zA-Z0-9_]*)\(/', $jsonpResponse, $match)) { $callbackName = $match[1]; } }

// Extract JSON from JSONP wrapper $pattern = '/^' . preg_quote($callbackName) . '\s*\(\s*(.+)\s*\)\s*;?\s*$/';

if (preg_match($pattern, $jsonpResponse, $match)) { $jsonString = $match[1]; return json_decode($jsonString, true); }

// Try to extract anything between parentheses if (preg_match('/\((.+)\)/s', $jsonpResponse, $match)) { return json_decode($match[1], true); }

return null; } ?> ```

Step 10: Test and Validate JSON Operations

Create comprehensive test suite:

```php <?php // test_json.php - JSON handling test suite

function runJsonTests() { $tests = [ 'valid_object' => '{"name":"John","age":30}', 'valid_array' => '[1,2,3,4,5]', 'valid_nested' => '{"user":{"name":"John","profile":{"age":30}}}', 'valid_unicode' => '{"name":"John \u00e9"}', 'valid_escape' => '{"text":"Hello\\nWorld"}', 'valid_null' => 'null', 'valid_boolean' => 'true', 'valid_number' => '123.45',

// Invalid cases 'invalid_trailing_comma' => '{"name":"John",}', 'invalid_single_quotes' => '{\'name\':\'John\'}', 'invalid_unquoted_key' => '{name:"John"}', 'invalid_missing_brace' => '{"name":"John"', 'invalid_extra_brace' => '{"name":"John"}}', 'invalid_comment' => '{"name":"John"/*comment*/}', 'invalid_control_char' => '{"name":"John\x00"}', ];

$results = [];

foreach ($tests as $name => $json) { $result = json_decode($json, true); $error = json_last_error(); $errorMsg = json_last_error_msg();

$results[$name] = [ 'json' => $json, 'decoded' => $result, 'error_code' => $error, 'error_message' => $errorMsg, 'valid' => $error === JSON_ERROR_NONE ];

// Test cleanup functions on invalid JSON if ($error !== JSON_ERROR_NONE) { $cleaned = cleanupJson($json); $cleanedResult = json_decode($cleaned, true);

$results[$name]['cleanup_attempt'] = [ 'cleaned_json' => $cleaned, 'result' => $cleanedResult, 'success' => $cleanedResult !== null ]; } }

return $results; }

function printTestResults($results) { $passed = 0; $failed = 0;

foreach ($results as $name => $result) { $status = $result['valid'] ? 'PASS' : 'FAIL';

if (strpos($name, 'invalid_') === 0) { // Expected to fail if (!$result['valid']) { $status = 'PASS (expected fail)'; $passed++; } else { $status = 'FAIL (should have been invalid)'; $failed++; } } else { // Expected to pass if ($result['valid']) { $passed++; } else { $failed++; } }

echo "$name: $status\n";

if (!$result['valid'] && strpos($name, 'invalid_') !== 0) { echo " Error: {$result['error_message']}\n"; }

if (isset($result['cleanup_attempt'])) { $cleanup = $result['cleanup_attempt']; echo " Cleanup: {$cleanup['success']}\n"; } }

echo "\nResults: $passed passed, $failed failed\n"; }

// Run tests $results = runJsonTests(); printTestResults($results);

// Encode/decode roundtrip test function testRoundtrip($data) { $json = json_encode($data); $decoded = json_decode($json, true);

return $decoded === $data; }

$roundtripTests = [ 'simple_array' => [1, 2, 3], 'nested_array' => [['a', 'b'], ['c', 'd']], 'object_like' => ['name' => 'John', 'age' => 30], 'with_null' => ['name' => 'John', 'email' => null], 'with_boolean' => ['active' => true, 'deleted' => false], 'unicode' => ['name' => 'José García'], ];

foreach ($roundtripTests as $name => $data) { $result = testRoundtrip($data); echo "Roundtrip $name: " . ($result ? 'PASS' : 'FAIL') . "\n"; }

// Benchmark large JSON function benchmarkJsonDecode($json, $iterations = 100) { $start = microtime(true);

for ($i = 0; $i < $iterations; $i++) { json_decode($json, true); }

$end = microtime(true);

return ($end - $start) / $iterations; }

// Create large JSON for testing $largeJson = json_encode(range(1, 10000)); $time = benchmarkJsonDecode($largeJson, 10); echo "Large JSON decode time: " . round($time * 1000, 2) . " ms\n"; ?> ```

Checklist

StepActionVerified
1Added JSON validation before decode
2Implemented proper error handling with json_last_error
3Fixed common JSON syntax issues
4Handled encoding and UTF-8 problems
5Added robust API response parsing
6Implemented streaming for large JSON
7Created JSON debugging tools
8Built robust JSON encoder
9Handled special JSON edge cases
10Created comprehensive test suite
11Added JSON_THROW_ON_ERROR for PHP 7.3+
12Tested with malformed input scenarios

Verify the Fix

  1. 1.Test basic JSON decode:
  2. 2.```php
  3. 3.<?php
  4. 4.$validJson = '{"name":"John","age":30}';
  5. 5.$data = json_decode($validJson, true);
  6. 6.var_dump($data); // Should show array

$invalidJson = '{"name":"John",}'; $data = json_decode($invalidJson, true); echo json_last_error_msg(); // Should show error

$cleaned = preg_replace('/,\s*}/', '}', $invalidJson); $data = json_decode($cleaned, true); var_dump($data); // Should work now ?> ```

  1. 1.Test error handling:
  2. 2.```php
  3. 3.<?php
  4. 4.try {
  5. 5.$data = json_decode($badJson, true, 512, JSON_THROW_ON_ERROR);
  6. 6.} catch (JsonException $e) {
  7. 7.echo "Caught: " . $e->getMessage();
  8. 8.}
  9. 9.?>
  10. 10.`
  11. 11.Validate UTF-8 handling:
  12. 12.```bash
  13. 13.# Create test file with various encodings
  14. 14.cat > /tmp/test_json_encoding.php << 'EOF'
  15. 15.<?php
  16. 16.$tests = [
  17. 17.'UTF-8' => '{"name":"José García"}',
  18. 18.'Invalid UTF-8' => '{"name":"Jos\xE9"}',
  19. 19.'Unicode escape' => '{"name":"Jos\u00E9"}',
  20. 20.];

foreach ($tests as $label => $json) { $data = json_decode($json, true); echo "$label: " . ($data ? 'decoded' : json_last_error_msg()) . "\n"; } EOF

php /tmp/test_json_encoding.php ```

  1. 1.Test API response parsing:
  2. 2.```bash
  3. 3.# Test with mock API response
  4. 4.curl -s "https://httpbin.org/json" | php -r "
  5. 5.\$response = file_get_contents('php://stdin');
  6. 6.\$data = json_decode(\$response, true);
  7. 7.if (json_last_error() === JSON_ERROR_NONE) {
  8. 8.echo 'Valid API response\n';
  9. 9.print_r(\$data);
  10. 10.} else {
  11. 11.echo 'Invalid JSON: ' . json_last_error_msg() . '\n';
  12. 12.}
  13. 13."
  14. 14.`
  15. 15.Run full test suite:
  16. 16.```bash
  17. 17.php test_json.php
  18. 18.`
  19. 19.Monitor JSON errors in production:
  20. 20.```bash
  21. 21.# Watch for JSON errors
  22. 22.tail -f /var/log/php-fpm/error.log | grep -i "json"

# Count JSON decode errors grep -c "json_decode" /var/log/php-fpm/error.log grep -c "JSON decode error" /var/log/php/app_errors.log ```

  • [Fix PHP Undefined Index Offset Error](/articles/fix-php-undefined-index-offset-error)
  • [Fix PHP Class Not Found Autoload](/articles/fix-php-class-not-found-autoload)
  • [Fix API Response Parsing Error](/articles/fix-api-response-parsing-error)
  • [Fix UTF-8 Encoding Characters Broken](/articles/fix-utf8-encoding-characters-broken)
  • [Fix PHP File Read Encoding Issues](/articles/fix-php-file-read-encoding)
  • [Fix Laravel API JSON Response Error](/articles/fix-laravel-api-json-response-error)
  • [Fix WordPress REST API JSON Error](/articles/fix-wordpress-rest-api-json-error)
  • [Fix Guzzle HTTP Client Response Parse](/articles/fix-guzzle-http-client-response-parse)
  • [Fix PHP Serialized Data Corruption](/articles/fix-php-serialized-data-corruption)
  • [Fix JSON Big Integer Overflow](/articles/fix-json-big-integer-overflow)