{"record":{"id":"b87bbd3986c0e14e","repo":"jmoiron/sqlx","slug":"expected-s-but-got-s","errorCode":null,"errorMessage":"expected %s but got %s","messagePattern":"expected (.+?) but got (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"sqlx.go","lineNumber":875,"sourceCode":"\n\treturn r.Err()\n}\n\ntype rowsi interface {\n\tClose() error\n\tColumns() ([]string, error)\n\tErr() error\n\tNext() bool\n\tScan(...interface{}) error\n}\n\n// structOnlyError returns an error appropriate for type when a non-scannable\n// struct is expected but something else is given\nfunc structOnlyError(t reflect.Type) error {\n\tisStruct := t.Kind() == reflect.Struct\n\tisScanner := reflect.PtrTo(t).Implements(_scannerInterface)\n\tif !isStruct {\n\t\treturn fmt.Errorf(\"expected %s but got %s\", reflect.Struct, t.Kind())\n\t}\n\tif isScanner {\n\t\treturn fmt.Errorf(\"structscan expects a struct dest but the provided struct type %s implements scanner\", t.Name())\n\t}\n\treturn fmt.Errorf(\"expected a struct, but struct %s has no exported fields\", t.Name())\n}\n\n// scanAll scans all rows into a destination, which must be a slice of any\n// type.  It resets the slice length to zero before appending each element to\n// the slice.  If the destination slice type is a Struct, then StructScan will\n// be used on each row.  If the destination is some other kind of base type,\n// then each row must only have one column which can scan into that type.  This\n// allows you to do something like:\n//\n//\trows, _ := db.Query(\"select id from people;\")\n//\tvar ids []int\n//\tscanAll(rows, &ids, false)\n//","sourceCodeStart":857,"sourceCodeEnd":893,"githubUrl":"https://github.com/jmoiron/sqlx/blob/41dac167fdad5e3fd81d66cafba0951dc6823a30/sqlx.go#L857-L893","documentation":"structOnlyError reports why StructScan rejected a destination: the destination type is not a struct at all (it's a primitive, map, slice, etc.). StructScan only maps rows onto struct types, so any other kind is refused, echoing the expected reflect.Struct kind versus what was given.","triggerScenarios":"Calling StructScan/Get/Select with a destination whose dereferenced kind is not reflect.Struct, forcing the non-struct branch of structOnlyError.","commonSituations":"Passing a *string, *int, or map instead of a struct pointer to StructScan; a helper function typed interface{} receiving the wrong dest; confusing Get (scalars allowed) with StructScan (struct required).","solutions":["Pass a pointer to a struct (e.g. &User{}) as the destination.","Use rows.Scan or Get with a scalar dest if you intend to scan a single primitive column.","Check the dest type before calling StructScan in generic code."],"exampleFix":"// before\nvar name string\nrows.StructScan(&name)\n\n// after\ntype Row struct { Name string `db:\"name\"` }\nvar r Row\nrows.StructScan(&r)","handlingStrategy":"type-guard","validationCode":"if reflectx.Deref(reflect.TypeOf(dest)).Kind() != reflect.Struct {\n    return errors.New(\"StructScan requires a struct destination\")\n}","typeGuard":"func isStructDest(dest interface{}) bool {\n    t := reflect.TypeOf(dest)\n    return t != nil && t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct\n}","tryCatchPattern":"if err := rows.StructScan(dest); err != nil {\n    if strings.Contains(err.Error(), \"expected struct but got\") {\n        return fmt.Errorf(\"wrong dest type for StructScan: %w\", err)\n    }\n    return err\n}","preventionTips":["Use Get/Scan for scalar destinations, StructScan only for structs.","Type helper functions as *T where T is a struct.","Avoid interface{} parameters for scan destinations in shared code."],"tags":["sqlx","struct-scan","type-mismatch","reflect"],"backgroundTag":"scan-dest-type-mismatch","analyzedSha":"41dac167fdad5e3fd81d66cafba0951dc6823a30","analyzedAt":"2026-09-03T06:10:20.382Z","contentChangedAt":"2026-09-03T06:10:20.382Z","schemaVersion":2},"datasetVersion":"2026-09-10T12:17:11.382Z"}