siyuan-note/siyuan · error

map[string]apicontract.JSONValue

Error message

map[string]apicontract.JSONValue

What it means

SystemSetUILayout (/api/system/setUILayout) stores the layout as map[string]JSONValue; this rewritten error surfaces when that map cannot be produced from the `layout` field. The message replaces the internal type name map[string]apicontract.JSONValue with conf.UILayout so the user sees the real config type. It means the `layout` value (or one of its entries) has a shape that does not match the expected UI-layout structure.

Solutions

  1. Send layout as a JSON object matching the conf.UILayout shape (typically {"<paneID>":{...layout...}})
  2. Export the current layout via the UI and reuse that structure as a template
  3. If unsure, omit `layout` or send null to reset rather than sending a guessed object

Example fix

// before
{"layout": "[{\"id\":\"root\"}]"}
// after
{"layout": {"root": {"direction": 1, "instance": "..."}}}
Defensive patterns

Strategy: type-guard

Validate before calling

const validLayout = l => l === undefined || l === null || (typeof l === 'object' && !Array.isArray(l));

Type guard

const isUILayout = (v) => typeof v === 'object' && (v === null || !Array.isArray(v));

Try / catch

const res = await fetchPost('/api/system/setUILayout', {layout}); if (res.code !== 0) { /* res.data.layoutError or res.msg describes the field mismatch */ }

Prevention

When it happens

Trigger: POSTing /api/system/setUILayout with layout as a non-object (string/array), or with nested entries whose types conflict during legacy JSON value decoding; an absent layout field decodes as null and is tolerated, so the error comes from an actually present but malformed value.

Common situations: A plugin or script persisting a custom layout object that predates the current schema; hand-edited layout JSON with wrong nesting; restoring a layout blob from an older SiYuan version.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/505d54a98342956c. Report an issue: GitHub.

Appendix: source

Thrown at kernel/apicontract/system_input.go:31

		config, err := decodeSettingConfig(reader, SystemGetChangelog.Definition().Path, false)
		if err != nil {
			return r, nil
		}
		r.Force, err = legacyField[bool](config.fields, "force", "Boolean", false)
		return
	}
	SystemSetUILayout.decodeRequest = func(reader io.Reader) (r SystemUILayoutRequest, err error) {
		config, err := decodeSettingConfig(reader, SystemSetUILayout.Definition().Path, false)
		if err != nil {
			return r, err
		}
		raw := config.fields["layout"]
		if len(raw) == 0 {
			raw = []byte("null")
		}
		r.Layout, r.layoutError = legacyJSONValue[map[string]JSONValue](raw)
		if r.layoutError != nil {
			r.layoutError = errors.New(strings.ReplaceAll(r.layoutError.Error(), "map[string]apicontract.JSONValue", "conf.UILayout"))
		}
		return
	}
	SystemLoginAuth.decodeRequest = func(reader io.Reader) (r SystemLoginAuthRequest, err error) {
		config, err := decodeSettingConfig(reader, SystemLoginAuth.Definition().Path, false)
		if err != nil {
			return r, err
		}
		r.AuthCode, r.authCodeError = legacyField[string](config.fields, "authCode", "String", true)
		r.Captcha, r.captchaError = legacyField[string](config.fields, "captcha", "String", false)
		r.RememberMe, _ = legacyField[bool](config.fields, "rememberMe", "Boolean", false)
		return
	}
	SystemOIDCStart.decodeRequest = func(reader io.Reader) (r SystemOIDCStartRequest, err error) {
		r.parseError = json.NewDecoder(reader).Decode(&r)
		return
	}
	SystemOIDCMobileCallback.decodeRequest = func(reader io.Reader) (r SystemOIDCMobileRequest, err error) {

View on GitHub (pinned to 9f775e8a12)