semaphoreui/semaphore · error

expected map for field

Error message

expected map for field %s but got %T

What it means

For map-typed struct fields, the assigned value must itself be a Go map. If the value under the matching JSON key is any other kind (string, int, slice, bool), this error is returned. Unlike the struct-field case, a JSON-array-style string is not parsed here — the value must already be a map.

Solutions

  1. Supply a real map in the source: map[string]any{"host": "localhost"}.
  2. If the value is a JSON object string, json.Unmarshal it into map[string]any before assignment.
  3. Verify the config key and struct field type agree — change one so the shapes match.
  4. For env-sourced maps, parse key=value pairs into a map yourself before calling AssignMapToStruct.

Example fix

// before
m := map[string]any{"db": "host=localhost"} // field is map[string]any
// after
m := map[string]any{"db": map[string]any{"host": "localhost"}}
Defensive patterns

Strategy: type-guard

Validate before calling

if v, ok := m["db"]; ok {
  if _, isMap := v.(map[string]any); !isMap {
    return fmt.Errorf("db must be an object, got %T", v)
  }
}

Type guard

func isMapValue(v any) bool {
  rv := reflect.ValueOf(v)
  return rv.Kind() == reflect.Map
}

Try / catch

if err := util.AssignMapToStruct(m, &cfg); err != nil {
  if strings.Contains(err.Error(), "expected map for field") {
    log.Fatalf("map field requires object value: %v", err)
  }
}

Prevention

When it happens

Trigger: Assigning m["db"] = "host=localhost" (a string) or a slice to a field of type map[string]X via AssignMapToStruct.

Common situations: Env-var-driven config where a map was flattened to a delimited string; config schema changed a field from scalar/slice to map; hand-built maps where the wrong key received a scalar.

Related errors


AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07). Data as JSON: /api/errors/aa69e477af9f809b. Report an issue: GitHub.

Appendix: source

Thrown at util/config.go:1253

									return fmt.Errorf("cannot assign element of type %s to slice element of type %s", srcElemVal.Type(), fieldElemType)
								}
								dstElem = reflect.ValueOf(newVal)
							}
						}

						newSlice = reflect.Append(newSlice, dstElem)
					}

					fieldValue.Set(newSlice)
				case reflect.Map:
					if fieldValue.IsNil() {
						mapValue := reflect.MakeMap(fieldValue.Type())
						fieldValue.Set(mapValue)
					}

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

					for _, key := range val.MapKeys() {
						mapElemValue := val.MapIndex(key)
						mapElemType := fieldValue.Type().Elem()

						srcVal := fieldValue.MapIndex(key)
						var mapElem reflect.Value
						if srcVal.IsValid() {
							mapElem = cloneStruct(srcVal)
						} else {
							mapElem = reflect.New(mapElemType).Elem()
						}

						if mapElemType.Kind() == reflect.Struct {
							if err := assignMapToStructRecursive(mapElemValue.Interface().(map[string]any), mapElem); err != nil {
								return err
							}

View on GitHub (pinned to 1774ccb71a)