{"record":{"id":"7417409509c3efd5","repo":"siyuan-note/siyuan","slug":"field-s-has-an-invalid-type-w","errorCode":null,"errorMessage":"Field [%s] has an invalid type: %w","messagePattern":"Field \\[(.+?)\\] has an invalid type: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/apicontract/decode.go","lineNumber":114,"sourceCode":"\t\t} else if has(\"legacyobject\") {\n\t\t\t// 配置补丁先按 JSON 数字语义归一化，再按结构体字段名兼容绑定。\n\t\t\tvar normalized any\n\t\t\tdecodeErr = json.Unmarshal(raw, &normalized)\n\t\t\tif decodeErr == nil {\n\t\t\t\tvar data []byte\n\t\t\t\tdata, decodeErr = json.Marshal(normalized)\n\t\t\t\tif decodeErr == nil {\n\t\t\t\t\tdecodeErr = json.Unmarshal(data, value.Field(i).Addr().Interface())\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tdecodeErr = decodeRequestValue(raw, value.Field(i))\n\t\t}\n\t\tif decodeErr != nil {\n\t\t\tif has(\"ignoretype\") {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"Field [%s] has an invalid type: %w\", name, decodeErr)\n\t\t}\n\t\tif has(\"trim\") {\n\t\t\ttrimmed := strings.TrimSpace(value.Field(i).String())\n\t\t\tif trimmed == \"\" {\n\t\t\t\treturn fmt.Errorf(\"Field [%s] must not be empty\", name)\n\t\t\t}\n\t\t\tvalue.Field(i).SetString(trimmed)\n\t\t}\n\t\tfor _, option := range strings.Split(field.Tag.Get(\"api\"), \",\") {\n\t\t\tif strings.HasPrefix(option, \"enum=\") {\n\t\t\t\tif field.Type.Kind() != reflect.String {\n\t\t\t\t\treturn fmt.Errorf(\"unsupported enum field: %s\", name)\n\t\t\t\t}\n\t\t\t\tfound := false\n\t\t\t\tfor _, choice := range strings.Split(strings.TrimPrefix(option, \"enum=\"), \"|\") {\n\t\t\t\t\tif value.Field(i).String() == choice {\n\t\t\t\t\t\tfound = true\n\t\t\t\t\t\tbreak","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/apicontract/decode.go#L96-L132","documentation":"After locating a field's raw JSON, decodeRequestFields unmarshals it into the struct field; when the JSON value's type does not fit the Go field type (string into int, object into array, etc.) the underlying error is wrapped as 'Field [x] has an invalid type'. The 'ignoretype' api option suppresses this, but by default it is a hard failure.","triggerScenarios":"Sending \"id\": 123 when the contract expects a string; sending an object where an array is declared; sending a number with fractional part into an int field; wrong nesting level of the payload.","commonSituations":"Dynamic JS code producing numbers instead of strings for IDs; API version drift where a field changed type; copy-pasted payload examples from a different endpoint; plugin authors bypassing generated typed clients.","solutions":["Send the value with the JSON type declared by the contract field (check quotes, brackets)","Use the generated typed client/schema so mismatches are caught at compile time","If the field must tolerate varied types, add `ignoretype` to its api tag and handle coercion in the handler","Cast/convert on the client side before sending (e.g. String(id) for string-typed IDs)"],"exampleFix":"// before\nfetchPost(url, {id: 123})\n// after\nfetchPost(url, {id: \"123\"})","handlingStrategy":"type-guard","validationCode":"function assertTypes(payload, schema) {\n  for (const [k, expected] of Object.entries(schema)) {\n    const v = payload[k]\n    if (v === undefined) continue\n    if (expected === \"string\" && typeof v !== \"string\") throw new TypeError(`Field ${k} must be a string`)\n    if (expected === \"array\" && !Array.isArray(v)) throw new TypeError(`Field ${k} must be an array`)\n    if (expected === \"number\" && typeof v !== \"number\") throw new TypeError(`Field ${k} must be a number`)\n  }\n}","typeGuard":"function isString(v: unknown): v is string { return typeof v === \"string\" }\nfunction isStringArray(v: unknown): v is string[] { return Array.isArray(v) && v.every(isString) }","tryCatchPattern":"try { await fetchPost(path, payload) } catch (e) { const m = e.message.match(/Field \\[(.+?)\\] has an invalid type/); if (m) { console.error(`Check declared type of field \"${m[1]}\" in the contract schema`); return } throw e }","preventionTips":["Generate client types from the contract schema and use them","Quote string IDs explicitly; never let JS numbers leak into string fields","Round-trip test payloads against the schema in CI","Avoid hand-writing payloads for endpoints with changed field types"],"tags":["type-mismatch","json","validation","api-contract"],"backgroundTag":"type-mismatch","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"}