gin-gonic/gin · error · errUnknownType

unknown type

Error message

unknown type

What it means

Returned by setWithProperType's default branch (binding/form_mapping.go:385) when Gin's reflection-driven form/URI binder encounters a struct field whose reflect.Kind it cannot handle. The package declares this sentinel only for genuinely unsupported scalar kinds (complex64/128, chan, func, unsafe.Pointer, interface) since the explicit switch covers int/uint/bool/float/string/struct(time,multipart.FileHeader)/map/ptr.

Source

Thrown at binding/form_mapping.go:23

package binding

import (
	"encoding"
	"errors"
	"fmt"
	"maps"
	"mime/multipart"
	"reflect"
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin/codec/json"
	"github.com/gin-gonic/gin/internal/bytesconv"
)

var (
	errUnknownType = errors.New("unknown type")

	// ErrConvertMapStringSlice can not convert to map[string][]string
	ErrConvertMapStringSlice = errors.New("can not convert to map slices of strings")

	// ErrConvertToMapString can not convert to map[string]string
	ErrConvertToMapString = errors.New("can not convert to map of strings")
)

func mapURI(ptr any, m map[string][]string) error {
	return mapFormByTag(ptr, m, "uri")
}

func mapForm(ptr any, form map[string][]string) error {
	return mapFormByTag(ptr, form, "form")
}

func MapFormWithTag(ptr any, form map[string][]string, tag string) error {
	return mapFormByTag(ptr, form, tag)

View on GitHub (pinned to 34dac209ff)

Solutions

  1. Inspect the struct: find fields typed complex*, chan, func, interface, or unsafe.Pointer and remove them from the binding DTO (or mark them with the tag `form:"-"` / `uri:"-"`).
  2. Move non-bindable fields out of the request struct into a separate domain type populated after binding.
  3. If you need an interface field, wrap it in a concrete type that implements encoding.TextUnmarshaler / binding.BindUnmarshaler so trySetCustom handles it before the default branch.

Example fix

// before
type Req struct {
    Handler func()  `form:"handler"`
    Value   complex128 `form:"value"`
}
// after
type Req struct {
    Value string `form:"value"`
}
Defensive patterns

Strategy: validation

Validate before calling

func isBindableStruct(t reflect.Type) error {
    for i := 0; i < t.NumField(); i++ {
        f := t.Field(i)
        if f.Tag.Get("form") == "-" && f.Tag.Get("uri") == "-" { continue }
        switch f.Type.Kind() {
        case reflect.Chan, reflect.Func, reflect.Complex64, reflect.Complex128, reflect.UnsafePointer:
            return fmt.Errorf("field %s has unsupported kind %s", f.Name, f.Type.Kind())
        }
    }
    return nil
}
// at startup: if err := isBindableStruct(reflect.TypeOf(Req{})); err != nil { log.Fatal(err) }

Type guard

func isBindableKind(k reflect.Kind) bool {
    switch k {
    case reflect.Chan, reflect.Func, reflect.Complex64, reflect.Complex128,
         reflect.UnsafePointer, reflect.Interface:
        return false
    }
    return true
}

Prevention

When it happens

Trigger: Calling c.ShouldBindWith / c.ShouldBindUri / c.ShouldBindQuery on a struct that has a field of kind complex64, complex128, chan, func, interface, or unsafe.Pointer, with a matching form/uri/query key present in the request.

Common situations: Defining a request DTO with a func field or chan field by mistake; embedding an interface-typed field expecting Gin to deserialize it; copying a domain model that contains non-POD fields into a binding struct.

Related errors


AI-assisted analysis of gin-gonic/gin@34dac209ff (2026-08-04). Data as JSON: /data/errors/22cd985fdcf2e7ec.json. Report an issue: GitHub.