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

  1. Fix the backup JSON so the map field is an object with string keys.
  2. Align the Go struct field type with the actual JSON shape (slice vs map).
  3. Regenerate the backup from the source system instead of manual editing.
  4. 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

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


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)