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

  1. Send the value with the JSON type declared by the contract field (check quotes, brackets)
  2. Use the generated typed client/schema so mismatches are caught at compile time
  3. If the field must tolerate varied types, add `ignoretype` to its api tag and handle coercion in the handler
  4. 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

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


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
						break

View on GitHub (pinned to 9f775e8a12)