{"record":{"id":"5808cdb701f0483f","repo":"semaphoreui/semaphore","slug":"expected-object-for-struct-got-t","errorCode":null,"errorMessage":"expected object for struct, got %T","messagePattern":"expected object for struct, got %T","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"services/project/backup_marshal.go","lineNumber":198,"sourceCode":"\t}\n}\n\nfunc unmarshalValueWithBackupTags(data any, v reflect.Value) error {\n\t// Handle pointers\n\tif v.Kind() == reflect.Ptr {\n\t\t// Initialize pointer if it's nil\n\t\tif v.IsNil() {\n\t\t\tv.Set(reflect.New(v.Type().Elem()))\n\t\t}\n\t\treturn unmarshalValueWithBackupTags(data, v.Elem())\n\t}\n\n\t// Handle structs\n\tif v.Kind() == reflect.Struct {\n\t\t// Data should be a map\n\t\tm, ok := data.(map[string]any)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"expected object for struct, got %T\", data)\n\t\t}\n\t\treturn unmarshalStructWithBackupTags(m, v)\n\t}\n\n\t// Handle slices and arrays\n\tif v.Kind() == reflect.Slice {\n\t\tdataSlice, ok := data.([]any)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"expected array for slice, got %T\", data)\n\t\t}\n\t\tslice := reflect.MakeSlice(v.Type(), len(dataSlice), len(dataSlice))\n\t\tfor i := 0; i < len(dataSlice); i++ {\n\t\t\telem := slice.Index(i)\n\t\t\tif err := unmarshalValueWithBackupTags(dataSlice[i], elem); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tv.Set(slice)","sourceCodeStart":180,"sourceCodeEnd":216,"githubUrl":"https://github.com/semaphoreui/semaphore/blob/1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa/services/project/backup_marshal.go#L180-L216","documentation":"unmarshalValueWithBackupTags (services/project/backup_marshal.go:198) handles struct destinations by requiring the corresponding JSON data to be a map[string]any (a JSON object). If the data is any other type - string, number, array, bool, or null - it returns \"expected object for struct, got %T\" before recursing into unmarshalStructWithBackupTags.","triggerScenarios":"During backup restore, a struct-kind field is matched with backup JSON whose value is not an object - e.g. the field maps to a JSON string/array/number, or the whole payload passed to Unmarshal is not a JSON object while the target is a struct.","commonSituations":"Backup file where a nested entity was serialized as an array or scalar instead of an object; passing the wrong data element to Unmarshal (e.g. a JSON array of objects into a struct target); hand-edited or corrupted backup JSON; version drift changing a field from object to array.","solutions":["Inspect the backup JSON at the reported field and change the value to a JSON object ({...}) matching the struct shape.","Verify you pass the correct top-level object to Unmarshal - if the backup root is an array, unmarshal into a slice target instead.","Make the Go field a slice ([]T) if the data is legitimately an array.","Re-export the backup with the current tool version so the JSON shapes match the struct definitions."],"exampleFix":"// before (backup.json)\n\"inventory\": [\"inv1\", \"inv2\"]   // array, target is struct\n// after\n\"inventory\": {\"name\": \"inv1\", \"project_id\": 1}","handlingStrategy":"type-guard","validationCode":"var raw any\njson.Unmarshal(backupData, &raw)\nif _, ok := raw.(map[string]any); !ok {\n    return errors.New(\"backup root must be a JSON object\")\n}","typeGuard":"func isJSONObject(v any) bool { _, ok := v.(map[string]any); return ok }","tryCatchPattern":"if err := project.Unmarshal(data, &target); err != nil {\n    if strings.Contains(err.Error(), \"expected object for struct\") {\n        return fmt.Errorf(\"backup JSON shape does not match struct: %w\", err)\n    }\n    return err\n}","preventionTips":["Ensure struct fields map to JSON objects, not arrays or scalars.","Unmarshal JSON arrays into slice targets, not structs.","Validate backup JSON structure against the entity schema before restoring."],"tags":["go","json","unmarshal","shape-mismatch","struct"],"backgroundTag":"unexpected-response-shape","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"}