siyuan-note/siyuan · error
Field [ ] has an invalid type
Error message
Field [%s] has an invalid type: %w
What it means
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.
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)
Example fix
// before
fetchPost(url, {id: 123})
// after
fetchPost(url, {id: "123"}) Defensive patterns
Strategy: type-guard
Validate before calling
function assertTypes(payload, schema) {
for (const [k, expected] of Object.entries(schema)) {
const v = payload[k]
if (v === undefined) continue
if (expected === "string" && typeof v !== "string") throw new TypeError(`Field ${k} must be a string`)
if (expected === "array" && !Array.isArray(v)) throw new TypeError(`Field ${k} must be an array`)
if (expected === "number" && typeof v !== "number") throw new TypeError(`Field ${k} must be a number`)
}
} Type guard
function isString(v: unknown): v is string { return typeof v === "string" }
function isStringArray(v: unknown): v is string[] { return Array.isArray(v) && v.every(isString) } Try / catch
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 } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
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
- createEmptyParagraph must be a boolean
- Field [rootIDs] should be of type [Array]
- Field [ ] must not be null
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/7417409509c3efd5.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/apicontract/decode.go:114
} else if has("legacyobject") {
// 配置补丁先按 JSON 数字语义归一化,再按结构体字段名兼容绑定。
var normalized any
decodeErr = json.Unmarshal(raw, &normalized)
if decodeErr == nil {
var data []byte
data, decodeErr = json.Marshal(normalized)
if decodeErr == nil {
decodeErr = json.Unmarshal(data, value.Field(i).Addr().Interface())
}
}
} else {
decodeErr = decodeRequestValue(raw, value.Field(i))
}
if decodeErr != nil {
if has("ignoretype") {
continue
}
return fmt.Errorf("Field [%s] has an invalid type: %w", name, decodeErr)
}
if has("trim") {
trimmed := strings.TrimSpace(value.Field(i).String())
if trimmed == "" {
return fmt.Errorf("Field [%s] must not be empty", name)
}
value.Field(i).SetString(trimmed)
}
for _, option := range strings.Split(field.Tag.Get("api"), ",") {
if strings.HasPrefix(option, "enum=") {
if field.Type.Kind() != reflect.String {
return fmt.Errorf("unsupported enum field: %s", name)
}
found := false
for _, choice := range strings.Split(strings.TrimPrefix(option, "enum="), "|") {
if value.Field(i).String() == choice {
found = true
breakView on GitHub (pinned to 9f775e8a12)