siyuan-note/siyuan · error
Field [rootIDs] should be of type [Array]
Error message
Field [rootIDs] should be of type [Array]
What it means
The storage (SetWorkspaceData / rootIDs-style) decoder accepts an optional 'rootIDs' field but, if present, it must be a JSON array of strings. A non-array value (object, string, number) fails with 'Field [rootIDs] should be of type [Array]'. Non-string or empty entries are silently skipped rather than erroring.
Solutions
- Send rootIDs as a JSON array of strings, e.g. ["20240101120000-abcdefg"]
- Wrap a single ID: rootIDs: [id] instead of rootIDs: id
- Pass rootIDs:null or omit it to indicate 'all'
- Filter out null/undefined entries before serializing
Example fix
// before
body: JSON.stringify({ rootIDs: '20240101120000-abcdefg' })
// after
body: JSON.stringify({ rootIDs: ['20240101120000-abcdefg'] }) Defensive patterns
Strategy: type-guard
Validate before calling
function assertRootIDs(v: unknown): string[] {
if (v === null || v === undefined) return [];
if (!Array.isArray(v)) throw new Error('rootIDs must be an array of string IDs');
return v.filter((id): id is string => typeof id === 'string' && id !== '');
} Type guard
function isStringArray(v: unknown): v is string[] { return Array.isArray(v) && v.every((x) => typeof x === 'string'); } Try / catch
try { await setStorage(data); } catch (e) { if (String(e).includes('rootIDs') && String(e).includes('Array')) { data.rootIDs = [].concat(data.rootIDs); retry(); } } Prevention
- Always wrap a single ID in an array before sending
- Filter null/undefined/empty entries out of the array
- Type rootIDs as string[] in client code
- Pass null or omit rootIDs to mean 'no filter'
When it happens
Trigger: Calling the storage endpoint with rootIDs set to a single string, an object, or a number instead of an array of document ID strings; or rootIDs:null (which is accepted, not an error).
Common situations: Clients passing a single ID instead of wrapping it in an array, JS objects used as ID sets serialized as JSON objects, or older API usage where rootIDs was a comma-separated string.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- createDocTree definition must be a list
- createDocTree document must be a dictionary
- element [ ]
- Field [ ] has an invalid type
- Field [ ] should be of type [Array]
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/90c5592e5ed71d17.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/apicontract/storage.go:23
"encoding/json"
"errors"
"io"
"strings"
)
func init() {
BatchUpdateRecentDocCloseTime.decodeRequest = func(reader io.Reader) (request RecentDocsUpdateRequest, err error) {
fields, err := blockRequestFields(reader, "/api/storage/batchUpdateRecentDocCloseTime")
if err != nil {
return request, err
}
raw := fields["rootIDs"]
if len(raw) == 0 || bytes.Equal(raw, []byte("null")) {
return request, nil
}
var entries []json.RawMessage
if json.Unmarshal(raw, &entries) != nil {
return request, errors.New("Field [rootIDs] should be of type [Array]")
}
for _, entry := range entries {
var id string
if json.Unmarshal(entry, &id) == nil && id != "" {
request.RootIDs = append(request.RootIDs, id)
}
}
return
}
plain := PatchViewState
PatchViewState.decodeRequest = func(reader io.Reader) (ViewStatePatchRequest, error) {
request, err := plain.Decode(reader)
if err != nil {
switch {
case strings.HasPrefix(err.Error(), "Field [values]"):
err = errors.New("Field [values]: should be of type [Object]")
case strings.HasPrefix(err.Error(), "Field [removeKeys]"):
err = errors.New("Field [removeKeys]: each element should be a non-empty String")View on GitHub (pinned to 9f775e8a12)