semaphoreui/semaphore · error

cannot assign value of type %T to field

Error message

cannot assign value of type %T to field %s of type %s

What it means

assignMapToStructRecursive maps a map[string]any onto a struct via reflection. When a struct field's JSON-matching value passes the 'is a map' check but fails the typed assertion to map[string]any (it is a map with different key/value types, e.g. map[string]string), this error is returned. It signals the value's concrete Go type cannot feed the recursive struct assignment for that field.

Solutions

  1. Convert the value to map[string]any before calling AssignMapToStruct (copy keys/values into a new map[string]any).
  2. Unmarshal the source JSON into map[string]any (json.Unmarshal into any) instead of a typed map so nested values have the right type.
  3. Normalize decoder output (e.g. convert map[interface{}]interface{} from YAML to map[string]any) before assignment.
  4. If the field should not be a struct, fix the struct definition or the config key so types agree.

Example fix

// before
m := map[string]any{"db": map[string]string{"host": "localhost"}}
util.AssignMapToStruct(m, &cfg)
// after
util.AssignMapToStruct(map[string]any{
  "db": map[string]any{"host": "localhost"},
}, &cfg)
Defensive patterns

Strategy: type-guard

Validate before calling

func isStringAnyMap(v any) bool { _, ok := v.(map[string]any); return ok }
if !isStringAnyMap(m["db"]) { return fmt.Errorf("field db must be map[string]any") }

Type guard

func asStringAnyMap(v any) (map[string]any, bool) {
  if m, ok := v.(map[string]any); ok {
    return m, true
  }
  return nil, false
}

Try / catch

err := util.AssignMapToStruct(m, &cfg)
if err != nil {
  var typeErr *fmt.Errorf // Inspect err.Error() for "cannot assign value of type"
  _ = typeErr
  log.Fatalf("config map shape invalid: %v", err)
}

Prevention

When it happens

Trigger: Calling util.AssignMapToStruct with a value for a struct-typed field that is a reflect.Map but not a map[string]any — e.g. passing map[string]string{...} or a map[string]int under a key that maps to a nested struct field.

Common situations: Building config maps by hand with typed maps instead of map[string]any; loading config from a decoder (e.g. yaml/toml) that produces map[string]interface{} alternatives like map[interface{}]interface{}; JSON that was unmarshaled into a typed map rather than any.

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

Appendix: source

Thrown at util/config.go:1171

			jsonTag = strings.Split(jsonTag, ",")[0]
		}

		if value, ok := m[jsonTag]; ok {
			fieldValue := structValue.FieldByName(field.Name)
			if fieldValue.CanSet() {

				val := reflect.ValueOf(value)

				switch fieldValue.Kind() {
				case reflect.Struct:

					if val.Kind() != reflect.Map {
						return fmt.Errorf("expected map for nested struct field %s but got %T", field.Name, value)
					}

					mapValue, ok := value.(map[string]any)
					if !ok {
						return fmt.Errorf("cannot assign value of type %T to field %s of type %s", value, field.Name, field.Type)
					}
					err := assignMapToStructRecursive(mapValue, fieldValue)
					if err != nil {
						return err
					}
				case reflect.Slice:
					// Handle slice assignment
					fieldElemType := fieldValue.Type().Elem()
					var sourceSlice reflect.Value
					if val.Kind() == reflect.Slice || val.Kind() == reflect.Array {
						sourceSlice = val
					} else if val.Kind() == reflect.String {
						// Try to parse JSON array from string
						str := val.String()
						// First, try to unmarshal into []any
						var anyArr []any
						if err := json.Unmarshal([]byte(str), &anyArr); err == nil {
							sourceSlice = reflect.ValueOf(anyArr)

View on GitHub (pinned to 1774ccb71a)