{"record":{"id":"2df8209b65673923","repo":"semaphoreui/semaphore","slug":"expected-object-for-map-got-t","errorCode":null,"errorMessage":"expected object for map, got %T","messagePattern":"expected object for map, got %T","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"services/project/backup_marshal.go","lineNumber":234,"sourceCode":"\t\tv.Set(slice)\n\t\treturn nil\n\t}\n\n\tif v.Type() == reflect.TypeOf(db.MapStringAnyField{}) {\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 map[string]interface{}, got %T\", data)\n\t\t}\n\t\tv.Set(reflect.ValueOf(db.MapStringAnyField(m)))\n\t\treturn nil\n\t}\n\n\t// Handle maps\n\tif v.Kind() == reflect.Map {\n\t\tdataMap, ok := data.(map[string]any)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"expected object for map, got %T\", data)\n\t\t}\n\t\tmapType := v.Type()\n\t\tmapValue := reflect.MakeMap(mapType)\n\t\tfor key, value := range dataMap {\n\t\t\tkeyVal := reflect.ValueOf(key).Convert(mapType.Key())\n\t\t\tvalVal := reflect.New(mapType.Elem()).Elem()\n\t\t\tif err := unmarshalValueWithBackupTags(value, valVal); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tmapValue.SetMapIndex(keyVal, valVal)\n\t\t}\n\t\tv.Set(mapValue)\n\t\treturn nil\n\t}\n\n\t// Handle basic types\n\treturn setBasicType(data, v)\n}","sourceCodeStart":216,"sourceCodeEnd":252,"githubUrl":"https://github.com/semaphoreui/semaphore/blob/1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa/services/project/backup_marshal.go#L216-L252","documentation":"For any map-kind Go field (reflect.Map) other than MapStringAnyField, the unmarshaller requires the JSON value to be a map[string]any. If data is any other JSON type it returns this error. It indicates the backup JSON does not match the declared map field type.","triggerScenarios":"project.Unmarshal where a map-typed struct field received an array/scalar JSON value; also reachable recursively when unmarshalling slice elements or map values that are themselves maps.","commonSituations":"Hand-crafted or migrated backup files with wrong JSON shapes; version drift where a field changed from map to slice; generated backups from a different schema version.","solutions":["Fix the backup JSON so the map field is an object with string keys.","Align the Go struct field type with the actual JSON shape (slice vs map).","Regenerate the backup from the source system instead of manual editing.","Add a pre-check that decodes the backup with encoding/json and validates field shapes before calling Unmarshal."],"exampleFix":"// before\n\"environment\": [\"A=1\"]\n// after\n\"environment\": {\"A\": \"1\"}","handlingStrategy":"validation","validationCode":"var raw map[string]json.RawMessage\njson.Unmarshal(backupJSON, &raw)\nfor field, v := range raw {\n    var obj map[string]any\n    if needsMap(field) && json.Unmarshal(v, &obj) != nil {\n        return fmt.Errorf(\"field %q must be a JSON object with string keys\", field)\n    }\n}","typeGuard":"func isStringKeyedMap(v any) bool { _, ok := v.(map[string]any); return ok }","tryCatchPattern":"if err := project.Unmarshal(data, &dst); err != nil {\n    if strings.Contains(err.Error(), \"expected object for map\") {\n        // fix the JSON shape of the map-typed field\n    }\n    return err\n}","preventionTips":["Match struct field types to the JSON shape produced by the exporter.","Test Unmarshal/Unmarshal round-trips in CI with representative backups.","Regenerate backups after any model field-type change."],"tags":["go","reflection","unmarshal","backup-restore"],"backgroundTag":"incompatible-source-type","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"}