{"record":{"id":"a4c6fdfd43b80f1d","repo":"AykutSarac/jsoncrack.com","slug":"unable-to-parse-data","errorCode":null,"errorMessage":"Unable to parse data.","messagePattern":"Unable to parse data\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/jsoncrack-react/src/canvasHelpers.ts","lineNumber":87,"sourceCode":"  | { kind: \"ok\"; graph: GraphData; syntaxErrorCount: number }\n  | { kind: \"above-limit\"; total: number }\n  | { kind: \"error\"; error: Error };\n\n/** Parse a JSON text into a graph, returning a discriminated result instead of throwing or touching React state. */\nexport const parseJsonGraph = (\n  jsonText: string,\n  maxRenderableNodes: number\n): ParseJsonGraphResult => {\n  try {\n    const graph = parseGraph(jsonText);\n    if (graph.nodes.length > maxRenderableNodes) {\n      return { kind: \"above-limit\", total: graph.nodes.length };\n    }\n    return { kind: \"ok\", graph, syntaxErrorCount: graph.errors.length };\n  } catch (error) {\n    return {\n      kind: \"error\",\n      error: error instanceof Error ? error : new Error(\"Unable to parse data.\"),\n    };\n  }\n};\n\n/** Build a map from edge id → target node id for O(1) lookups in edge renderers. */\nexport const buildEdgeTargetMap = (edges: GraphData[\"edges\"]): Map<string, string> => {\n  const targetById = new Map<string, string>();\n  for (let i = 0; i < edges.length; i += 1) {\n    const edge = edges[i];\n    targetById.set(edge.id, edge.to);\n  }\n  return targetById;\n};\n\n/** Toggle reaflow's `dragging` class on the canvas div to suppress pointer events during long-press panning. */\nexport const setCanvasDragging = (container: HTMLElement | null, dragging: boolean): void => {\n  const canvas = container?.querySelector(\".jsoncrack-canvas\") as HTMLElement | null;\n  if (!canvas) return;","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/AykutSarac/jsoncrack.com/blob/3c9af69e23c635356293b6b28cf4cd0af10d1059/packages/jsoncrack-react/src/canvasHelpers.ts#L69-L105","documentation":"The fallback Error constructed in parseJsonGraph (canvasHelpers.ts:87) when the synchronous parseGraph call throws something that is NOT an Error instance (e.g. a string, number, or null thrown by a dependency). parseJsonGraph wraps the whole parse in try/catch and returns a discriminated { kind:\"error\", error } result instead of throwing, so callers never see a raw throw. When the thrown value is already an Error it is preserved verbatim; only non-Error throws get this generic message.","triggerScenarios":"parseGraph (parser.ts) delegates to jsonc-parser's parseTree/getNodePath and to calculateNodeSize. If any of those throw a non-Error value, or if a future change throws a primitive, the catch builds `new Error(\"Unable to parse data.\")`. Realistically this path is rare because jsonc-parser does not throw on bad input (it reports errors in the collector); it would require a bug in calculateNodeSize or a corrupted node value (e.g. a circular value reaching value.toString()).","commonSituations":"A dependency regression that throws a primitive; extremely large/deeply-nested JSON causing a stack overflow inside traversal that surfaces as a thrown string; monkey-patched prototypes interfering with node.value.toString().","solutions":["Inspect the actual thrown value: temporarily log `error` in the catch to see if it carries a real message before the generic fallback applies.","Validate/size-limit input before parsing (guard against pathological depth or size).","Ensure the thrown value is an Error upstream so its message is preserved instead of being replaced by the generic text.","If reproducing, isolate whether calculateNodeSize or getNodePath is the real throw site by unit-testing parseGraph directly."],"exampleFix":"// before\nreturn {\n  kind: \"error\",\n  error: error instanceof Error ? error : new Error(\"Unable to parse data.\"),\n};\n\n// after — preserve non-Error throw context for diagnostics\nreturn {\n  kind: \"error\",\n  error:\n    error instanceof Error\n      ? error\n      : new Error(`Unable to parse data. (thrown: ${typeof error} ${String(error)})`),\n};","handlingStrategy":"try-catch","validationCode":"// Reject pathological inputs before parsing (depth/size guards)\nexport function isSafeToParse(text: string, maxBytes = 5_000_000, maxDepth = 500): boolean {\n  if (text.length > maxBytes) return false;\n  let depth = 0;\n  for (const ch of text) {\n    if (ch === \"{\" || ch === \"[\") depth++;\n    if (ch === \"}\" || ch === \"]\") depth--;\n    if (depth > maxDepth) return false;\n  }\n  return true;\n}","typeGuard":"// Narrow the discriminated parse result\nexport function isParseError(r: { kind: string }): r is { kind: \"error\"; error: Error } {\n  return r.kind === \"error\";\n}","tryCatchPattern":"// parseJsonGraph never throws — handle the discriminated result\nconst result = parseJsonGraph(jsonText, maxRenderableNodes);\nif (result.kind === \"error\") {\n  // result.error.message may be \"Unable to parse data.\" for non-Error throws\n  report(result.error);\n}","preventionTips":["Treat any non-Error thrown value as a bug in upstream parser utilities — fix the throw site.","Size/depth-limit input before parsing to avoid stack overflows in traversal.","Unit-test parseGraph directly with adversarial inputs to surface the real throw."],"tags":["json","parsing","jsoncrack-react","defensive"],"backgroundTag":null,"analyzedSha":"3c9af69e23c635356293b6b28cf4cd0af10d1059","analyzedAt":"2026-08-12T19:00:27.891Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}