siyuan-note/siyuan · error
block operation result must be text, block IDs or null
Error message
block operation result must be text, block IDs or null
What it means
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.
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
Example fix
// before
{"result": {"ids": ["20240101120000-abc"]}}
// after
{"result": ["20240101120000-abc"]} Defensive patterns
Strategy: type-guard
Validate before calling
function isValidResult(v) { return v === null || typeof v === "string" || (Array.isArray(v) && v.every(x => typeof x === "string")); } Type guard
function isBlockOpResult(v) { return v === null || typeof v === "string" || (Array.isArray(v) && v.every(x => typeof x === "string")); } Try / catch
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; } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- block ID must be text
- SQL value must be a JSON scalar
- createDocTree definition must be a list
- createDocTree document must be a dictionary
- Field [id] should be of type [String]
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/dedc26a10cf44e02.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/apicontract/block_transaction.go:86
func (r BlockOperationResult) MarshalJSON() ([]byte, error) {
if r.text != nil {
return json.Marshal(*r.text)
}
return json.Marshal(r.ids)
}
func (r *BlockOperationResult) UnmarshalJSON(data []byte) error {
data = bytes.TrimSpace(data)
*r = BlockOperationResult{}
if bytes.Equal(data, []byte("null")) {
return nil
}
if len(data) > 0 && data[0] == '"' {
return json.Unmarshal(data, &r.text)
}
var values []json.RawMessage
if len(data) == 0 || data[0] != '[' {
return fmt.Errorf("block operation result must be text, block IDs or null")
}
if err := json.Unmarshal(data, &values); err != nil {
return err
}
r.ids = make([]string, len(values))
for i, value := range values {
if bytes.Equal(bytes.TrimSpace(value), []byte("null")) {
return fmt.Errorf("block ID must be text")
}
if err := json.Unmarshal(value, &r.ids[i]); err != nil {
return err
}
}
return nil
}
type BlockOperation struct {
Action string `json:"action" api:"enum=delete|insert|update|foldHeading|unfoldHeading|setAttrs|moveOutlineHeading|appendInsert|prependInsert"`View on GitHub (pinned to 9f775e8a12)