semaphoreui/semaphore · error
expected object for map, got %T
Error message
expected object for map, got %T
What it means
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.
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.
Example fix
// before
"environment": ["A=1"]
// after
"environment": {"A": "1"} Defensive patterns
Strategy: validation
Validate before calling
var raw map[string]json.RawMessage
json.Unmarshal(backupJSON, &raw)
for field, v := range raw {
var obj map[string]any
if needsMap(field) && json.Unmarshal(v, &obj) != nil {
return fmt.Errorf("field %q must be a JSON object with string keys", field)
}
} Type guard
func isStringKeyedMap(v any) bool { _, ok := v.(map[string]any); return ok } Try / catch
if err := project.Unmarshal(data, &dst); err != nil {
if strings.Contains(err.Error(), "expected object for map") {
// fix the JSON shape of the map-typed field
}
return err
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.
Related errors
- expected array for slice, got %T
- expected object for map[string]interface
- cannot set value
- unsupported kind
- expected bool for field, got %T
AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07).
Data as JSON: /api/errors/2df8209b65673923.
Report an issue: GitHub.
Appendix: source
Thrown at services/project/backup_marshal.go:234
v.Set(slice)
return nil
}
if v.Type() == reflect.TypeOf(db.MapStringAnyField{}) {
// Data should be a map
m, ok := data.(map[string]any)
if !ok {
return fmt.Errorf("expected object for map[string]interface{}, got %T", data)
}
v.Set(reflect.ValueOf(db.MapStringAnyField(m)))
return nil
}
// Handle maps
if v.Kind() == reflect.Map {
dataMap, ok := data.(map[string]any)
if !ok {
return fmt.Errorf("expected object for map, got %T", data)
}
mapType := v.Type()
mapValue := reflect.MakeMap(mapType)
for key, value := range dataMap {
keyVal := reflect.ValueOf(key).Convert(mapType.Key())
valVal := reflect.New(mapType.Elem()).Elem()
if err := unmarshalValueWithBackupTags(value, valVal); err != nil {
return err
}
mapValue.SetMapIndex(keyVal, valVal)
}
v.Set(mapValue)
return nil
}
// Handle basic types
return setBasicType(data, v)
}View on GitHub (pinned to 1774ccb71a)