{"record":{"id":"e5d030c7a6223123","repo":"siyuan-note/siyuan","slug":"generated-tree-s-is-not-valid-json","errorCode":null,"errorMessage":"generated tree [%s] is not valid JSON","messagePattern":"generated tree \\[(.+?)\\] is not valid JSON","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/import_obsidian.go","lineNumber":2350,"sourceCode":"\ttree.Root.SetIALAttr(\"title\", doc.Title)\n\ttree.Root.SetIALAttr(\"updated\", util.TimeFromID(doc.ID))\n\ttree.Root.RemoveIALAttrsByPrefix(\"custom-\")\n}\n\nfunc writeObsidianTempTree(docsTemp string, tree *parse.Tree) error {\n\tif tree == nil || tree.Root == nil {\n\t\treturn errors.New(\"cannot stage an empty tree\")\n\t}\n\tif tree.Root.FirstChild == nil {\n\t\ttree.Root.AppendChild(treenode.NewParagraph(\"\"))\n\t}\n\ttreenode.UpgradeSpec(tree)\n\ttree.Root.SetIALAttr(\"type\", \"doc\")\n\tluteEngine := util.NewLute()\n\trenderer := render.NewJSONRenderer(tree, luteEngine.RenderOptions, luteEngine.ParseOptions)\n\tdata := renderer.Render()\n\tif !json.Valid(data) {\n\t\treturn fmt.Errorf(\"generated tree [%s] is not valid JSON\", tree.HPath)\n\t}\n\tif !util.UseSingleLineSave {\n\t\tvar buffer bytes.Buffer\n\t\tif err := json.Indent(&buffer, data, \"\", \"\\t\"); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdata = buffer.Bytes()\n\t}\n\ttarget := filepath.Join(docsTemp, filepath.FromSlash(strings.TrimPrefix(tree.Path, \"/\")))\n\tif err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {\n\t\treturn err\n\t}\n\treturn filelock.WriteFile(target, data)\n}\n\nfunc copyStableObsidianFile(source *obsidianSourceFile, destination string) error {\n\tif err := validateObsidianSourceMetadata(source); err != nil {\n\t\treturn err","sourceCodeStart":2332,"sourceCodeEnd":2368,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/import_obsidian.go#L2332-L2368","documentation":"writeObsidianTempTree renders the staged parse tree with NewJSONRenderer and then verifies the output with json.Valid. If the rendered bytes are not valid JSON, the generated .sy document would be corrupt, so staging aborts and reports which document tree (by hpath) failed.","triggerScenarios":"renderer.Render() on a JSONRenderer produced bytes that fail json.Valid(data) — typically malformed input trees, a Lute/renderer bug, or binary/invalid data injected into node text during an Obsidian import.","commonSituations":"Importing notes containing unusual control characters or broken inline constructs that confuse the renderer; running with a mismatched/older Lute build whose renderer emits invalid JSON; the document hpath contains content that leaks into output incorrectly due to a renderer bug.","solutions":["Inspect the source note at the reported hpath for invalid characters or malformed Markdown and sanitize it, then re-import","Update SiYuan/Lute to the latest version in case of a JSON renderer regression","Isolate the failing document and import it alone to reproduce; bisect its content to the offending construct","File a bug with the offending markdown if valid input triggers it"],"exampleFix":"// before: renderer output trusted blindly\ndata := renderer.Render()\nif !json.Valid(data) {\n\treturn fmt.Errorf(\"generated tree [%s] is not valid JSON\", tree.HPath)\n}\n// after (caller): sanitize control characters before transform\nmarkdown = bytes.Map(func(r rune) rune {\n\tif unicode.IsControl(r) && r != '\\n' && r != '\\t' { return -1 }\n\treturn r\n}, markdown)","handlingStrategy":"validation","validationCode":"markdown = bytes.Map(func(r rune) rune {\n\tif unicode.IsControl(r) && r != '\\n' && r != '\\t' { return -1 }\n\treturn r\n}, markdown) // strip control chars before transform","typeGuard":null,"tryCatchPattern":"if err := writeObsidianTempTree(docsTemp, tree); err != nil && strings.Contains(err.Error(), \"not valid JSON\") {\n\t// isolate the document at err's hpath and bisect its content\n}","preventionTips":["Sanitize control characters from imported markdown before rendering","Keep the Lute build in sync with the kernel version","Test-import unusual notes (code blocks, HTML embeds) in isolation first"],"tags":["json","renderer","serialization","obsidian-import"],"backgroundTag":"json-serialization-failed","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}