{"record":{"id":"36857450227ef2f0","repo":"semaphoreui/semaphore","slug":"expected-number-for-field-got-t","errorCode":null,"errorMessage":"expected number for field, got %T","messagePattern":"expected number for field, got %T","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"services/project/backup_marshal.go","lineNumber":131,"sourceCode":"\t}\n\n\tswitch v.Kind() {\n\tcase reflect.Bool:\n\t\tb, ok := data.(bool)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"expected bool for field, got %T\", data)\n\t\t}\n\t\tv.SetBool(b)\n\tcase reflect.String:\n\t\ts, ok := data.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"expected string for field, got %T\", data)\n\t\t}\n\t\tv.SetString(s)\n\tcase reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:\n\t\tn, ok := toFloat64(data)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"expected number for field, got %T\", data)\n\t\t}\n\t\tv.SetInt(int64(n))\n\tcase reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:\n\t\tn, ok := toFloat64(data)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"expected number for field, got %T\", data)\n\t\t}\n\t\tv.SetUint(uint64(n))\n\tcase reflect.Float32, reflect.Float64:\n\t\tn, ok := toFloat64(data)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"expected number for field, got %T\", data)\n\t\t}\n\t\tv.SetFloat(n)\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported kind %v\", v.Kind())\n\t}\n\treturn nil","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/semaphoreui/semaphore/blob/1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa/services/project/backup_marshal.go#L113-L149","documentation":"In setBasicType (services/project/backup_marshal.go:131), for signed integer fields (Int..Int64), the data is run through toFloat64; if the value is not a JSON number (or numeric type), the function returns \"expected number for field, got %T\". toFloat64 only accepts float/int/uint variants, so strings, bools, nil, and objects all fail.","triggerScenarios":"unmarshalValueWithBackupTags encounters an int-kind struct field whose backup JSON value is a string (e.g. \"42\"), boolean, null, or nested object.","commonSituations":"Backups where numeric fields were serialized as strings by a producer tool or hand-editing; null values where an int was expected; schema changes turning a string field into int between versions.","solutions":["Edit the backup JSON so the field is an unquoted JSON number (\"42\" -> 42).","If the source data is a string of digits, convert it in the exporter or change the struct field to string.","Handle null explicitly: replace null with a numeric default (e.g. 0) in the backup, or make the field a pointer (*int) so nil data is handled upstream.","Re-export the backup with a matching tool version."],"exampleFix":"// before (backup.json)\n\"build_number\": \"42\"\n// after\n\"build_number\": 42","handlingStrategy":"validation","validationCode":"if v, ok := raw[\"build_number\"]; ok {\n    if _, ok := v.(float64); !ok { return fmt.Errorf(\"field build_number must be a number, got %T\", v) }\n}","typeGuard":"func isJSONNumber(v any) bool { switch v.(type) { case float64, float32, int, int64: return true }; return false }","tryCatchPattern":"if err := project.Unmarshal(data, &target); err != nil {\n    if strings.Contains(err.Error(), \"expected number for field\") {\n        return fmt.Errorf(\"numeric field has wrong type in backup: %w\", err)\n    }\n    return err\n}","preventionTips":["Never quote numbers in backup JSON.","Replace null with numeric defaults, or use pointer fields for optional ints.","Validate the backup file with a schema checker before restoring."],"tags":["go","json","unmarshal","type-mismatch","integer"],"backgroundTag":"type-mismatch","analyzedSha":"1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa","analyzedAt":"2026-09-07T11:00:33.293Z","contentChangedAt":"2026-09-07T11:00:33.293Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}