{"record":{"id":"a1e97efc744f5e78","repo":"siyuan-note/siyuan","slug":"field-removekeys-each-element-should-be-a-non-empty-string","errorCode":null,"errorMessage":"Field [removeKeys]: each element should be a non-empty String","messagePattern":"Field \\[removeKeys\\]: each element should be a non-empty String","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/apicontract/storage.go","lineNumber":41,"sourceCode":"\t\t\treturn request, errors.New(\"Field [rootIDs] should be of type [Array]\")\n\t\t}\n\t\tfor _, entry := range entries {\n\t\t\tvar id string\n\t\t\tif json.Unmarshal(entry, &id) == nil && id != \"\" {\n\t\t\t\trequest.RootIDs = append(request.RootIDs, id)\n\t\t\t}\n\t\t}\n\t\treturn\n\t}\n\tplain := PatchViewState\n\tPatchViewState.decodeRequest = func(reader io.Reader) (ViewStatePatchRequest, error) {\n\t\trequest, err := plain.Decode(reader)\n\t\tif err != nil {\n\t\t\tswitch {\n\t\t\tcase strings.HasPrefix(err.Error(), \"Field [values]\"):\n\t\t\t\terr = errors.New(\"Field [values]: should be of type [Object]\")\n\t\t\tcase strings.HasPrefix(err.Error(), \"Field [removeKeys]\"):\n\t\t\t\terr = errors.New(\"Field [removeKeys]: each element should be a non-empty String\")\n\t\t\t}\n\t\t}\n\t\treturn request, err\n\t}\n}\n\ntype RecentDocUpdateRequest struct {\n\tRootID string `json:\"rootID\" api:\"optional,nullable\"`\n}\ntype RecentDocsUpdateRequest struct {\n\tRootIDs []string `json:\"rootIDs\" api:\"optional,nullable\"`\n}\n\ntype StorageKeyRequest struct {\n\tKey string `json:\"key\" api:\"trim\"`\n}\ntype StorageKeysRequest struct {\n\tKeys []string `json:\"keys\"`","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/apicontract/storage.go#L23-L59","documentation":"This error is returned when decoding the request for POST /api/storage/setViewState. It is produced when the generic decoder rejects the `removeKeys` field and the wrapper rewrites the message. It means `removeKeys` was not an array whose every element is a non-empty string (e.g. an object, a scalar, or an array containing numbers, nulls, or empty strings).","triggerScenarios":"Calling /api/storage/setViewState with removeKeys like {\"removeKeys\":\"a\"}, {\"removeKeys\":[1,2]}, {\"removeKeys\":[\"\"]}, or {\"removeKeys\":[null]}.","commonSituations":"A client passing an object map of keys-to-delete instead of an array; a script building removeKeys from a Set and including an empty entry; sending JSON after a variable was left undefined and serialized oddly.","solutions":["Pass removeKeys as an array of non-empty strings: {\"key\":\"local\",\"removeKeys\":[\"k1\",\"k2\"]}","Filter out empty or non-string entries before sending","Omit removeKeys entirely if you have nothing to remove"],"exampleFix":"// before\n{\"key\":\"local\", \"removeKeys\":{\"k1\":true}}\n// after\n{\"key\":\"local\", \"removeKeys\":[\"k1\"]}","handlingStrategy":"validation","validationCode":"const ok = Array.isArray(p.removeKeys) && p.removeKeys.every(k => typeof k === 'string' && k.length > 0);","typeGuard":"const isNonEmptyStringArray = (v) => Array.isArray(v) && v.every(k => typeof k === 'string' && k.length > 0);","tryCatchPattern":"try { const res = await fetchPost('/api/storage/setViewState', payload); if (res.code !== 0) throw new Error(res.msg); } catch (e) { console.error(e.message); }","preventionTips":["removeKeys must be an array of strings, not an object map","Filter falsy/empty entries before sending","Omit removeKeys when not removing anything"],"tags":["json","validation","http-api","siyuan"],"backgroundTag":"invalid-argument-format","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"}