{"record":{"id":"1a23750608dca2db","repo":"phacility/phabricator","slug":"failed-to-json-decode-json-document-after-s","errorCode":null,"errorMessage":"Failed to \"json_decode(...)\" JSON document after successfully decoding it with \"phutil_json_decode(...).","messagePattern":"Failed to \"json_decode\\(\\.\\.\\.\\)\" JSON document after successfully decoding it with \"phutil_json_decode\\(\\.\\.\\.\\)\\.","errorType":"exception","errorClass":"PhabricatorDocumentEngineParserException","httpStatus":null,"severity":"error","filePath":"src/applications/files/document/PhabricatorJSONDocumentEngine.php","lineNumber":43,"sourceCode":"    if ($ref->isProbablyJSON()) {\n      return 1750;\n    }\n\n    return 500;\n  }\n\n  protected function newDocumentContent(PhabricatorDocumentRef $ref) {\n    $raw_data = $this->loadTextData($ref);\n\n    try {\n      $data = phutil_json_decode($raw_data);\n\n      // See T13635. \"phutil_json_decode()\" always turns JSON into a PHP array,\n      // and we lose the distinction between \"{}\" and \"[]\". This distinction is\n      // important when rendering a document.\n      $data = json_decode($raw_data, false);\n      if (!$data) {\n        throw new PhabricatorDocumentEngineParserException(\n          pht(\n            'Failed to \"json_decode(...)\" JSON document after successfully '.\n            'decoding it with \"phutil_json_decode(...).'));\n      }\n\n      if (preg_match('/^\\s*\\[/', $raw_data)) {\n        $content = id(new PhutilJSON())->encodeAsList($data);\n      } else {\n        $content = id(new PhutilJSON())->encodeFormatted($data);\n      }\n\n      $message = null;\n      $content = PhabricatorSyntaxHighlighter::highlightWithLanguage(\n        'json',\n        $content);\n    } catch (PhutilJSONParserException $ex) {\n      $message = $this->newMessage(\n        pht(","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/files/document/PhabricatorJSONDocumentEngine.php#L25-L61","documentation":"PhabricatorJSONDocumentEngine decodes twice: phutil_json_decode() first validates the JSON, then json_decode($raw_data, false) re-decodes it to preserve the object ('{}') vs list ('[]') distinction for rendering. This exception fires when the second decode yields a falsy PHP value — which happens for valid JSON whose root is a falsy literal: null, 0, false, \"\" or []. In other words it is a false-positive: the truthiness check (!$data) mistakes a valid falsy decode for a decoding failure.","triggerScenarios":"Uploading a .json file whose entire content is 'null', '0', 'false', '\"\"' or '[]'. Each is valid JSON, so phutil_json_decode() succeeds, but json_decode() returns null/0/''/empty array and the if (!$data) guard throws.","commonSituations":"Machine-generated artifacts that legitimately contain a bare scalar or empty list (e.g. '[]' from a linter with no findings, 'null' from an API dump); minimal hand-written JSON test files.","solutions":["Work around by giving the document a non-empty root: use an object with at least one member instead of a bare 'null', '0', or '[]'","Patch the engine to detect failure with a strict null check plus json_last_error() instead of truthiness (the upstream class of fix)","If rendering keeps failing, view/download the raw file instead of the formatted JSON view"],"exampleFix":"// before\n$data = json_decode($raw_data, false);\nif (!$data) {\n  throw new PhabricatorDocumentEngineParserException(\n    pht('Failed to \"json_decode(...)\" JSON document ...'));\n}\n\n// after\n$data = json_decode($raw_data, false);\nif ($data === null && json_last_error() !== JSON_ERROR_NONE) {\n  throw new PhabricatorDocumentEngineParserException(\n    pht('Failed to \"json_decode(...)\" JSON document ...'));\n}","handlingStrategy":"try-catch","validationCode":"$decoded = json_decode($raw, false);\n$valid = ($decoded !== null || json_last_error() === JSON_ERROR_NONE);\nif (!$valid) {\n  // do not hand the document to the JSON engine\n}","typeGuard":null,"tryCatchPattern":"catch PhabricatorDocumentEngineParserException specifically and fall back to plain-text rendering of the raw bytes; do not retry — the outcome is deterministic for a given document.","preventionTips":["Emit JSON documents with an object root and at least one member","Never test json_decode() results with truthiness; compare against null and json_last_error()"],"tags":["json","rendering","truthiness-bug","document-engine"],"backgroundTag":"json-decode-falsy-bug","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}