semaphoreui/semaphore · error

expected map for nested struct field

Error message

expected map for nested struct field %s but got %T

What it means

Returned by the reflective config-decoding helper in util/config.go when it walks into a nested struct field (by Go field name in the message) and the corresponding value in the source map is not itself a map. The %T shows the actual JSON type found (string, float64, []interface{}, bool). It is a type guard for structurally invalid config: nested sections must decode from JSON objects; any scalar or array where a section object is expected triggers this error.

Solutions

  1. Fix the config file/environment mapping so the named section is a JSON/YAML object with the nested field keys inside it
  2. Move mistyped keys that belong one level deeper under the proper parent section
  3. Check the %T hint — a string or number where the object is expected usually means a missing nesting level or a stray '=' style flat key
  4. Compare the section against config.schema.yaml to see the expected nested structure
Defensive patterns

Strategy: type-guard

When it happens

Trigger: Thrown at util/config.go:1166 when the library encounters an invalid state.

Common situations: See trigger scenarios.


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

Appendix: source

Thrown at util/config.go:1166

		jsonTag := field.Tag.Get("json")
		if jsonTag == "" {
			jsonTag = field.Name
		} else {
			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

View on GitHub (pinned to 1774ccb71a)