semaphoreui/semaphore · error

expected slice or json array string for field

Error message

expected slice or json array string for field %s but got %T

What it means

For slice-typed struct fields, assignMapToStructRecursive accepts a slice/array, a JSON array string, or (for []string fields) a bare string treated as one element. If the value is a string that is neither valid JSON array nor the field's element type is string, this error is returned. It means the string form cannot be turned into the target slice.

Solutions

  1. Format the string as a valid JSON array, e.g. "[8080,8443]" instead of "8080,8443".
  2. Pass a real Go slice (e.g. []int{8080, 8443}) in the map instead of a string.
  3. If the field is []string, a bare string is allowed as a single element — check the field's element type.
  4. Split the string yourself and build the typed slice before calling AssignMapToStruct.

Example fix

// before
m := map[string]any{"ports": "8080,8443"} // field is []int
// after
m := map[string]any{"ports": []any{8080, 8443}}
Defensive patterns

Strategy: validation

Validate before calling

var probe []any
if s, ok := m["ports"].(string); ok {
  if err := json.Unmarshal([]byte(s), &probe); err != nil {
    return fmt.Errorf("ports must be a JSON array string, got %q", s)
  }
}

Type guard

func isJSONArrayString(v any) bool {
  s, ok := v.(string)
  if !ok {
    return false
  }
  var arr []any
  return json.Unmarshal([]byte(s), &arr) == nil
}

Try / catch

if err := util.AssignMapToStruct(m, &cfg); err != nil {
  if strings.Contains(err.Error(), "expected slice or json array string") {
    log.Fatalf("slice field %v needs JSON array format", err)
  }
}

Prevention

When it happens

Trigger: Assigning a plain (non-JSON) string like "a,b,c" to a slice field whose element type is not string (e.g. []int, []time.Duration), or a malformed JSON array string to any slice field.

Common situations: Config values sourced from environment variables or files as comma-separated strings assigned to []int or []struct fields; a typo in a JSON array string (missing brackets); a numeric slice field receiving "8080,8443".

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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

Appendix: source

Thrown at util/config.go:1195

				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)
						} else if fieldElemType.Kind() == reflect.String {
							// Fallback: treat as single element string
							sourceSlice = reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf("")), 1, 1)
							sourceSlice.Index(0).SetString(str)
						} else {
							return fmt.Errorf("expected slice or json array string for field %s but got %T", field.Name, value)
						}
					} else {
						return fmt.Errorf("expected slice for field %s but got %T", field.Name, value)
					}

					// Build destination slice
					newSlice := reflect.MakeSlice(fieldValue.Type(), 0, sourceSlice.Len())
					for i := 0; i < sourceSlice.Len(); i++ {
						srcElemVal := sourceSlice.Index(i)
						// When source is []any, elements come as interface{}, unwrap reflect.Value
						if srcElemVal.Kind() == reflect.Interface && !srcElemVal.IsNil() {
							srcElemVal = reflect.ValueOf(srcElemVal.Interface())
						}

						var dstElem reflect.Value
						// Prepare destination element
						if fieldElemType.Kind() == reflect.Struct {
							dstElem = reflect.New(fieldElemType).Elem()

View on GitHub (pinned to 1774ccb71a)