{"record":{"id":"5f5eff3642c26a2e","repo":"MHSanaei/3x-ui","slug":"invalid-outbounds-json-w","errorCode":null,"errorMessage":"invalid outbounds JSON: %w","messagePattern":"invalid outbounds JSON: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/web/service/outbound/probe_http.go","lineNumber":126,"sourceCode":"// through a temp xray instance (UDP-transport outbounds are always forced to\n// the HTTP probe — a raw dial can't measure them).\nfunc (s *OutboundService) TestOutbound(outboundJSON string, testURL string, allOutboundsJSON string, mode string) (*TestOutboundResult, error) {\n\tvar ob map[string]any\n\tif err := json.Unmarshal([]byte(outboundJSON), &ob); err != nil {\n\t\treturn &TestOutboundResult{Mode: probeModeLabel(mode), Success: false, Error: fmt.Sprintf(\"Invalid outbound JSON: %v\", err)}, nil\n\t}\n\tresults := s.testOutboundsParsed([]map[string]any{ob}, testURL, allOutboundsJSON, mode)\n\treturn results[0], nil\n}\n\n// TestOutbounds probes a JSON array of outbounds and returns one result per\n// input, in input order, each carrying the outbound's tag. allOutboundsJSON\n// supplies the config context (sockopt.dialerProxy chains); testURL falls\n// back to the default probe URL when empty.\nfunc (s *OutboundService) TestOutbounds(outboundsJSON string, testURL string, allOutboundsJSON string, mode string) ([]*TestOutboundResult, error) {\n\tvar raw []json.RawMessage\n\tif err := json.Unmarshal([]byte(outboundsJSON), &raw); err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid outbounds JSON: %w\", err)\n\t}\n\tif len(raw) > maxBatchItems {\n\t\treturn nil, fmt.Errorf(\"too many outbounds in one request (max %d)\", maxBatchItems)\n\t}\n\titems := make([]map[string]any, len(raw))\n\tfor i, r := range raw {\n\t\tvar ob map[string]any\n\t\tif err := json.Unmarshal(r, &ob); err == nil {\n\t\t\titems[i] = ob\n\t\t}\n\t}\n\treturn s.testOutboundsParsed(items, testURL, allOutboundsJSON, mode), nil\n}\n\n// testOutboundsParsed splits items into the TCP lane (direct dials, bounded\n// worker pool) and the HTTP lane (one shared temp xray instance), runs both,\n// and returns results aligned with items. A nil item marks unparseable input.\nfunc (s *OutboundService) testOutboundsParsed(items []map[string]any, testURL string, allOutboundsJSON string, mode string) []*TestOutboundResult {","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/MHSanaei/3x-ui/blob/ad32144c42455696ea9f14e12168beac3e25f5d2/internal/web/service/outbound/probe_http.go#L108-L144","documentation":"TestOutbounds parses its outboundsJSON argument with json.Unmarshal into []json.RawMessage. Any string that is not a valid JSON array (or contains invalid JSON syntax) fails at this first gate. Note the outer layer only requires an array; individual non-object elements are silently left nil and handled later, so this error strictly means 'the top-level document is not parseable JSON array'.","triggerScenarios":"Calling TestOutbounds with a hand-typed string, a JSON object '{...}' instead of an array '[...]', YAML/base64 subscription content, or truncated output from an editor that dropped a bracket.","commonSituations":"Frontend sending a partially-edited outbound from the JSON editor tab; pasting a single outbound object instead of an array; smart-quote or trailing-comma corruption from copy/paste.","solutions":["Validate the payload is a JSON array before sending: json.Unmarshal into []json.RawMessage in a precheck","If testing a single outbound, use the single-outbound API/testOutbound path that wraps it in an array","Run the JSON through a linter/editor to catch truncation or trailing commas"],"exampleFix":"// before\nsvc.TestOutbounds(`{\"tag\":\"a\",\"protocol\":\"freedom\"}`, ...) // object, not array\n\n// after\nsvc.TestOutbounds(`[{\"tag\":\"a\",\"protocol\":\"freedom\"}]`, ...)","handlingStrategy":"validation","validationCode":"if !json.Valid([]byte(outboundsJSON)) {\n    return errors.New(\"outbounds payload is not valid JSON\")\n}\nvar probe []json.RawMessage\nif err := json.Unmarshal([]byte(outboundsJSON), &probe); err != nil {\n    return fmt.Errorf(\"payload must be a JSON array: %w\", err)\n}\nresults, err := svc.TestOutbounds(outboundsJSON, testURL, ctxJSON, mode)","typeGuard":"func isJSONArrayOfOutbounds(s string) bool {\n    var raw []json.RawMessage\n    return json.Unmarshal([]byte(s), &raw) == nil\n}","tryCatchPattern":"results, err := svc.TestOutbounds(payload, ...)\nif err != nil {\n    if strings.HasPrefix(err.Error(), \"invalid outbounds JSON\") {\n        // re-open editor with payload, highlight JSON error from %w chain\n    }\n}","preventionTips":["Always JSON-encode outbound objects with a marshaller instead of string concatenation","Validate with json.Valid in the frontend before enabling the test button"],"tags":["json","validation","outbounds","api"],"backgroundTag":null,"analyzedSha":"ad32144c42455696ea9f14e12168beac3e25f5d2","analyzedAt":"2026-08-15T11:13:23.905Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}