semaphoreui/semaphore · error

expected object for map[string]interface

Error message

expected object for map[string]interface{}, got %T

What it means

This branch handles fields of type db.MapStringAnyField during backup unmarshalling. If the JSON value for such a field is not a JSON object (map[string]any), the unmarshaller returns this error. It enforces that dynamic map fields in the backup are serialized as objects.

Solutions

  1. Edit the backup JSON so the field is a JSON object with string keys (e.g. {"key": "value"}).
  2. Check which struct field is db.MapStringAnyField and verify its serialized value matches an object shape.
  3. Re-export the backup from the original system to get correctly typed values.
  4. If the data truly is a list, change the model field type instead of the backup.

Example fix

// before
"meta": ["a", "b"]
// after
"meta": {"a": 1, "b": 2}
Defensive patterns

Strategy: validation

Validate before calling

var raw map[string]json.RawMessage
json.Unmarshal(backupJSON, &raw)
for _, field := range mapStringAnyFields { // fields of type db.MapStringAnyField
    var obj map[string]any
    if err := json.Unmarshal(raw[field], &obj); err != nil {
        return fmt.Errorf("field %q must be a JSON object", field)
    }
}

Type guard

func isJSONObject(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[string]interface{}") {
        // inspect the offending field in the backup JSON
    }
    return err
}

Prevention

When it happens

Trigger: project.Unmarshal on backup JSON where a db.MapStringAnyField field holds an array, string, number, bool, or null instead of a JSON object.

Common situations: Manual edits converting an object field to a list; tooling that flattens objects into arrays; schema drift after a field type changed between versions; copy-paste from a different entity that uses an array for the same field.

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/a5df2197a8fd6f72. Report an issue: GitHub.

Appendix: source

Thrown at services/project/backup_marshal.go:224

		if !ok {
			return fmt.Errorf("expected array for slice, got %T", data)
		}
		slice := reflect.MakeSlice(v.Type(), len(dataSlice), len(dataSlice))
		for i := 0; i < len(dataSlice); i++ {
			elem := slice.Index(i)
			if err := unmarshalValueWithBackupTags(dataSlice[i], elem); err != nil {
				return err
			}
		}
		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

View on GitHub (pinned to 1774ccb71a)