siyuan-note/siyuan · error

generated tree [%s] is not valid JSON

Error message

generated tree [%s] is not valid JSON

What it means

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.

Source

Thrown at kernel/model/import_obsidian.go:2350

	tree.Root.SetIALAttr("title", doc.Title)
	tree.Root.SetIALAttr("updated", util.TimeFromID(doc.ID))
	tree.Root.RemoveIALAttrsByPrefix("custom-")
}

func writeObsidianTempTree(docsTemp string, tree *parse.Tree) error {
	if tree == nil || tree.Root == nil {
		return errors.New("cannot stage an empty tree")
	}
	if tree.Root.FirstChild == nil {
		tree.Root.AppendChild(treenode.NewParagraph(""))
	}
	treenode.UpgradeSpec(tree)
	tree.Root.SetIALAttr("type", "doc")
	luteEngine := util.NewLute()
	renderer := render.NewJSONRenderer(tree, luteEngine.RenderOptions, luteEngine.ParseOptions)
	data := renderer.Render()
	if !json.Valid(data) {
		return fmt.Errorf("generated tree [%s] is not valid JSON", tree.HPath)
	}
	if !util.UseSingleLineSave {
		var buffer bytes.Buffer
		if err := json.Indent(&buffer, data, "", "\t"); err != nil {
			return err
		}
		data = buffer.Bytes()
	}
	target := filepath.Join(docsTemp, filepath.FromSlash(strings.TrimPrefix(tree.Path, "/")))
	if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
		return err
	}
	return filelock.WriteFile(target, data)
}

func copyStableObsidianFile(source *obsidianSourceFile, destination string) error {
	if err := validateObsidianSourceMetadata(source); err != nil {
		return err

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Inspect the source note at the reported hpath for invalid characters or malformed Markdown and sanitize it, then re-import
  2. Update SiYuan/Lute to the latest version in case of a JSON renderer regression
  3. Isolate the failing document and import it alone to reproduce; bisect its content to the offending construct
  4. File a bug with the offending markdown if valid input triggers it

Example fix

// before: renderer output trusted blindly
data := renderer.Render()
if !json.Valid(data) {
	return fmt.Errorf("generated tree [%s] is not valid JSON", tree.HPath)
}
// after (caller): sanitize control characters before transform
markdown = bytes.Map(func(r rune) rune {
	if unicode.IsControl(r) && r != '\n' && r != '\t' { return -1 }
	return r
}, markdown)
Defensive patterns

Strategy: validation

Validate before calling

markdown = bytes.Map(func(r rune) rune {
	if unicode.IsControl(r) && r != '\n' && r != '\t' { return -1 }
	return r
}, markdown) // strip control chars before transform

Try / catch

if err := writeObsidianTempTree(docsTemp, tree); err != nil && strings.Contains(err.Error(), "not valid JSON") {
	// isolate the document at err's hpath and bisect its content
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "JSON serialization failed", "not JSON serializable", "Failed to serialize": why JSON marshaling errors happen and how to fix them — this error's family across 46 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/e5d030c7a6223123. Report an issue: GitHub.