{"record":{"id":"b4db87d5ed9916f9","repo":"siyuan-note/siyuan","slug":"field-id-should-be-of-type-string-block-remaining","errorCode":null,"errorMessage":"Field [id] should be of type [String]","messagePattern":"Field \\[id\\] should be of type \\[String\\]","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/apicontract/block_remaining.go","lineNumber":176,"sourceCode":"\tif len(fields[\"level\"]) == 0 || bytes.Equal(fields[\"level\"], []byte(\"null\")) || json.Unmarshal(fields[\"level\"], &request.Level) != nil {\n\t\treturn request, errors.New(\"Field [level] should be of type [Number]\")\n\t}\n\tvar entries []json.RawMessage\n\tif raw := fields[\"ids\"]; len(raw) > 0 && !bytes.Equal(raw, []byte(\"null\")) && json.Unmarshal(raw, &entries) == nil {\n\t\trequest.IDs = []string{}\n\t\tseen := map[string]bool{}\n\t\tfor _, entry := range entries {\n\t\t\tvar id string\n\t\t\tif bytes.Equal(entry, []byte(\"null\")) || json.Unmarshal(entry, &id) != nil {\n\t\t\t\treturn request, errors.New(\"Field [ids] should contain strings\")\n\t\t\t}\n\t\t\tif !seen[id] {\n\t\t\t\trequest.IDs = append(request.IDs, id)\n\t\t\t\tseen[id] = true\n\t\t\t}\n\t\t}\n\t} else if len(fields[\"id\"]) == 0 || bytes.Equal(fields[\"id\"], []byte(\"null\")) || json.Unmarshal(fields[\"id\"], &request.ID) != nil {\n\t\treturn request, errors.New(\"Field [id] should be of type [String]\")\n\t}\n\treturn\n}\n","sourceCodeStart":158,"sourceCodeEnd":180,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/apicontract/block_remaining.go#L158-L180","documentation":"When /api/block/getHeadingLevelTransaction is called without a valid ids array, the kernel falls back to the singular id field, which must be a non-null JSON string. This error fires when id is missing, null, or not a string, i.e. neither a valid ids array nor a valid id string was supplied.","triggerScenarios":"POST /api/block/getHeadingLevelTransaction with {\"level\":2} and neither ids nor id; {\"level\":2,\"id\":null}; \"id\" given as a number; ids present but not a JSON array (e.g. a string), which skips the array branch and then fails the id fallback.","commonSituations":"Omitting both target fields after refactoring a single-block call to batch form (or vice versa); ids mistakenly sent as a comma-joined string so the array branch fails silently; a null id passed through from an unselected editor state.","solutions":["Provide either a valid ids array or a single id string block ID with every call","If sending ids, make sure it is a real JSON array (not a comma-joined string) so the correct branch is taken","Guard the call site so it only fires when a target block selection exists"],"exampleFix":"// before\nawait fetchPost(\"/api/block/getHeadingLevelTransaction\", { level: 2 });\n// after\nawait fetchPost(\"/api/block/getHeadingLevelTransaction\", { level: 2, id: \"20240101120000-abcdefg\" });","handlingStrategy":"validation","validationCode":"if (!Array.isArray(payload.ids) && typeof payload.id !== 'string') throw new TypeError('provide either ids array or id string');","typeGuard":"const hasTarget = (p) => (Array.isArray(p.ids) && p.ids.length > 0) || typeof p.id === 'string';","tryCatchPattern":"try { await fetchPost('/api/block/getHeadingLevelTransaction', payload); } catch (e) { if (/Field \\[id\\] should be of type \\[String\\]/.test(e?.message)) { /* read block ID from the active editor before retrying */ } }","preventionTips":["Always attach a target (ids array or id string) to heading-level requests","When batching, send ids as a JSON array; do not join IDs with commas into a string","Gate the call on the editor having an active block selection"],"tags":["api","validation","missing-field"],"backgroundTag":"missing-required-argument","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"}