{"record":{"id":"dedc26a10cf44e02","repo":"siyuan-note/siyuan","slug":"block-operation-result-must-be-text-block-ids-or-null","errorCode":null,"errorMessage":"block operation result must be text, block IDs or null","messagePattern":"block operation result must be text, block IDs or null","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/apicontract/block_transaction.go","lineNumber":86,"sourceCode":"func (r BlockOperationResult) MarshalJSON() ([]byte, error) {\n\tif r.text != nil {\n\t\treturn json.Marshal(*r.text)\n\t}\n\treturn json.Marshal(r.ids)\n}\n\nfunc (r *BlockOperationResult) UnmarshalJSON(data []byte) error {\n\tdata = bytes.TrimSpace(data)\n\t*r = BlockOperationResult{}\n\tif bytes.Equal(data, []byte(\"null\")) {\n\t\treturn nil\n\t}\n\tif len(data) > 0 && data[0] == '\"' {\n\t\treturn json.Unmarshal(data, &r.text)\n\t}\n\tvar values []json.RawMessage\n\tif len(data) == 0 || data[0] != '[' {\n\t\treturn fmt.Errorf(\"block operation result must be text, block IDs or null\")\n\t}\n\tif err := json.Unmarshal(data, &values); err != nil {\n\t\treturn err\n\t}\n\tr.ids = make([]string, len(values))\n\tfor i, value := range values {\n\t\tif bytes.Equal(bytes.TrimSpace(value), []byte(\"null\")) {\n\t\t\treturn fmt.Errorf(\"block ID must be text\")\n\t\t}\n\t\tif err := json.Unmarshal(value, &r.ids[i]); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\ntype BlockOperation struct {\n\tAction            string               `json:\"action\" api:\"enum=delete|insert|update|foldHeading|unfoldHeading|setAttrs|moveOutlineHeading|appendInsert|prependInsert\"`","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/apicontract/block_transaction.go#L68-L104","documentation":"UnmarshalJSON for the block operation result value accepts exactly three shapes: a JSON string (text), a JSON array of block IDs, or JSON null. Any other top-level shape (object, number, bool, empty input) produces this error. It keeps the discriminated-union result type strict so downstream code can safely switch on the payload kind.","triggerScenarios":"Decoding a block operation result that is a JSON object, number, boolean, or empty byte slice; the check `data[0] != '['` after the string fast-path fails for anything that is neither a quoted string nor an array.","commonSituations":"A server or proxy returning an error object {\"error\": ...} where a result value is expected; clients misusing the field with a numeric count instead of the documented text/IDs/null; truncated or empty response bodies.","solutions":["Send a JSON string if the operation result is text","Send a JSON array of block-ID strings if the operation produced blocks","Send literal null when there is no payload","Inspect the actual response body — an object usually means an error response was passed to the wrong decoder"],"exampleFix":"// before\n{\"result\": {\"ids\": [\"20240101120000-abc\"]}}\n// after\n{\"result\": [\"20240101120000-abc\"]}","handlingStrategy":"type-guard","validationCode":"function isValidResult(v) { return v === null || typeof v === \"string\" || (Array.isArray(v) && v.every(x => typeof x === \"string\")); }","typeGuard":"function isBlockOpResult(v) { return v === null || typeof v === \"string\" || (Array.isArray(v) && v.every(x => typeof x === \"string\")); }","tryCatchPattern":"try { result = decodeOpResult(raw); } catch (e) { if (String(e).includes(\"must be text, block IDs or null\")) { result = null; log.warn(\"unexpected op result shape\", raw); } else throw e; }","preventionTips":["Only serialize string, string-array, or null into operation result fields","Check response bodies for error objects before decoding as results","Use a discriminated-union type on the producer side","Add unit tests covering all three accepted shapes"],"tags":["json","unmarshal","type-mismatch"],"backgroundTag":"unexpected-response-shape","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}