{"record":{"id":"0871bd94971a3974","repo":"semaphoreui/semaphore","slug":"expected-slice-or-json-array-string-for-field-s-b","errorCode":null,"errorMessage":"expected slice or json array string for field %s but got %T","messagePattern":"expected slice or json array string for field (.+?) but got %T","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"util/config.go","lineNumber":1195,"sourceCode":"\t\t\t\tcase reflect.Slice:\n\t\t\t\t\t// Handle slice assignment\n\t\t\t\t\tfieldElemType := fieldValue.Type().Elem()\n\t\t\t\t\tvar sourceSlice reflect.Value\n\t\t\t\t\tif val.Kind() == reflect.Slice || val.Kind() == reflect.Array {\n\t\t\t\t\t\tsourceSlice = val\n\t\t\t\t\t} else if val.Kind() == reflect.String {\n\t\t\t\t\t\t// Try to parse JSON array from string\n\t\t\t\t\t\tstr := val.String()\n\t\t\t\t\t\t// First, try to unmarshal into []any\n\t\t\t\t\t\tvar anyArr []any\n\t\t\t\t\t\tif err := json.Unmarshal([]byte(str), &anyArr); err == nil {\n\t\t\t\t\t\t\tsourceSlice = reflect.ValueOf(anyArr)\n\t\t\t\t\t\t} else if fieldElemType.Kind() == reflect.String {\n\t\t\t\t\t\t\t// Fallback: treat as single element string\n\t\t\t\t\t\t\tsourceSlice = reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf(\"\")), 1, 1)\n\t\t\t\t\t\t\tsourceSlice.Index(0).SetString(str)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"expected slice or json array string for field %s but got %T\", field.Name, value)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn fmt.Errorf(\"expected slice for field %s but got %T\", field.Name, value)\n\t\t\t\t\t}\n\n\t\t\t\t\t// Build destination slice\n\t\t\t\t\tnewSlice := reflect.MakeSlice(fieldValue.Type(), 0, sourceSlice.Len())\n\t\t\t\t\tfor i := 0; i < sourceSlice.Len(); i++ {\n\t\t\t\t\t\tsrcElemVal := sourceSlice.Index(i)\n\t\t\t\t\t\t// When source is []any, elements come as interface{}, unwrap reflect.Value\n\t\t\t\t\t\tif srcElemVal.Kind() == reflect.Interface && !srcElemVal.IsNil() {\n\t\t\t\t\t\t\tsrcElemVal = reflect.ValueOf(srcElemVal.Interface())\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tvar dstElem reflect.Value\n\t\t\t\t\t\t// Prepare destination element\n\t\t\t\t\t\tif fieldElemType.Kind() == reflect.Struct {\n\t\t\t\t\t\t\tdstElem = reflect.New(fieldElemType).Elem()","sourceCodeStart":1177,"sourceCodeEnd":1213,"githubUrl":"https://github.com/semaphoreui/semaphore/blob/1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa/util/config.go#L1177-L1213","documentation":"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.","triggerScenarios":"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.","commonSituations":"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\".","solutions":["Format the string as a valid JSON array, e.g. \"[8080,8443]\" instead of \"8080,8443\".","Pass a real Go slice (e.g. []int{8080, 8443}) in the map instead of a string.","If the field is []string, a bare string is allowed as a single element — check the field's element type.","Split the string yourself and build the typed slice before calling AssignMapToStruct."],"exampleFix":"// before\nm := map[string]any{\"ports\": \"8080,8443\"} // field is []int\n// after\nm := map[string]any{\"ports\": []any{8080, 8443}}","handlingStrategy":"validation","validationCode":"var probe []any\nif s, ok := m[\"ports\"].(string); ok {\n  if err := json.Unmarshal([]byte(s), &probe); err != nil {\n    return fmt.Errorf(\"ports must be a JSON array string, got %q\", s)\n  }\n}","typeGuard":"func isJSONArrayString(v any) bool {\n  s, ok := v.(string)\n  if !ok {\n    return false\n  }\n  var arr []any\n  return json.Unmarshal([]byte(s), &arr) == nil\n}","tryCatchPattern":"if err := util.AssignMapToStruct(m, &cfg); err != nil {\n  if strings.Contains(err.Error(), \"expected slice or json array string\") {\n    log.Fatalf(\"slice field %v needs JSON array format\", err)\n  }\n}","preventionTips":["Prefer real Go slices over JSON-encoded strings in source maps.","Standardize on JSON arrays for list-shaped config in env vars and files.","Document the expected element type of each slice config field."],"tags":["reflection","config","go","slice","json"],"backgroundTag":"invalid-argument-format","analyzedSha":"1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa","analyzedAt":"2026-09-07T11:00:33.293Z","contentChangedAt":"2026-09-07T11:00:33.293Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}