{"record":{"id":"5ae15a295950568e","repo":"grpc/grpc-go","slug":"rls-json-unmarshal-failed-for-service-config-v","errorCode":null,"errorMessage":"rls: json unmarshal failed for service config %+v: %v","messagePattern":"rls: json unmarshal failed for service config %\\+v: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"balancer/rls/config.go","lineNumber":152,"sourceCode":"//\t   - `cache_size_bytes` field must have a value greater than 0, and if its\n//\t     value is greater than 5M, we cap it at 5M\n//\n//\t- routeLookupChannelServiceConfig:\n//\t  - if specified, must parse as valid service config\n//\n//\t- childPolicy:\n//\t  - must find a valid child policy with a valid config\n//\n//\t- childPolicyConfigTargetFieldName:\n//\t  - must be set and non-empty\nfunc (rlsBB) ParseConfig(c json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {\n\tif logger.V(2) {\n\t\tlogger.Infof(\"Received JSON service config: %v\", pretty.ToJSON(c))\n\t}\n\n\tcfgJSON := &lbConfigJSON{}\n\tif err := json.Unmarshal(c, cfgJSON); err != nil {\n\t\treturn nil, fmt.Errorf(\"rls: json unmarshal failed for service config %+v: %v\", string(c), err)\n\t}\n\n\tm := protojson.UnmarshalOptions{DiscardUnknown: true}\n\trlsProto := &rlspb.RouteLookupConfig{}\n\tif err := m.Unmarshal(cfgJSON.RouteLookupConfig, rlsProto); err != nil {\n\t\treturn nil, fmt.Errorf(\"rls: bad RouteLookupConfig proto %+v: %v\", string(cfgJSON.RouteLookupConfig), err)\n\t}\n\tlbCfg, err := parseRLSProto(rlsProto)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif sc := string(cfgJSON.RouteLookupChannelServiceConfig); sc != \"\" {\n\t\tparsed := internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(sc)\n\t\tif parsed.Err != nil {\n\t\t\treturn nil, fmt.Errorf(\"rls: bad control channel service config %q: %v\", sc, parsed.Err)\n\t\t}\n\t\tlbCfg.controlChannelServiceConfig = sc","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/balancer/rls/config.go#L134-L170","documentation":"Emitted from (rlsBB).ParseConfig (config.go:152) when the top-level JSON passed to the RLS balancer's ParseConfig does not unmarshal into the lbConfigJSON struct shape. json.Unmarshal into a struct fails on malformed JSON syntax, or on a type mismatch for one of the known fields (RouteLookupConfig / RouteLookupChannelServiceConfig / ChildPolicy / ChildPolicyConfigTargetFieldName). This is the first gate any RLS service config must pass.","triggerScenarios":"Calling balancer/rls ParseConfig (directly or via the gRPC service-config machinery / xDS RLS cluster specifier) with a JSON document that has a syntax error, or where RouteLookupConfig is not a JSON object/array of bytes, ChildPolicy is not a list of single-key maps, or ChildPolicyConfigTargetFieldName is not a string.","commonSituations":"Hand-authored service config JSON with a trailing comma, missing brace, or unquoted keys; a templating/YAML-to-JSON step that produced invalid JSON; passing the proto-encoded RLS config instead of the JSON-LB-config wrapper; an xDS control plane emitting a malformed RdsPlugin config.","solutions":["Run the config through a JSON validator (e.g. 'jq empty cfg.json') to surface syntax errors.","Confirm the top-level shape matches lbConfigJSON: an object with RouteLookupConfig (object), optional RouteLookupChannelServiceConfig (object), ChildPolicy (array of single-key objects), and ChildPolicyConfigTargetFieldName (string).","If the config arrives via xDS, inspect the raw LDS/RDS resource dump (e.g. xds-resolver debug log or channelz) to see the bytes the resolver actually delivered.","Regenerate the config from a known-good template or the RLS e2e test fixtures under balancer/rls/internal/test/e2e."],"exampleFix":"// before: trailing comma / type mismatch\n{\n  \"loadBalancingConfig\": [ { \"rls\": { \"routeLookupConfig\": { ... }, } } ]\n}\n\n// after: valid JSON, correct field types\n{\n  \"loadBalancingConfig\": [\n    { \"rls\": {\n        \"routeLookupConfig\": { ... },\n        \"childPolicyConfigTargetFieldName\": \"serviceName\",\n        \"childPolicy\": [ { \"round_robin\": {} } ]\n    } }\n  ]\n}","handlingStrategy":"validation","validationCode":"// Pre-validate that the RLS LB config unmarshals into the expected shape.\ntype lbConfigJSON struct {\n    RouteLookupConfig                json.RawMessage\n    RouteLookupChannelServiceConfig  json.RawMessage\n    ChildPolicy                      []map[string]json.RawMessage\n    ChildPolicyConfigTargetFieldName string\n}\nfunc validateRLSConfig(raw []byte) error {\n    var c lbConfigJSON\n    if err := json.Unmarshal(raw, &c); err != nil {\n        return fmt.Errorf(\"invalid RLS service config JSON: %w\", err)\n    }\n    if len(c.RouteLookupConfig) == 0 {\n        return errors.New(\"routeLookupConfig is required\")\n    }\n    return nil\n}","typeGuard":"func isParsableRLSConfig(raw []byte) bool {\n    var c struct{ RouteLookupConfig json.RawMessage }\n    return json.Unmarshal(raw, &c) == nil && len(c.RouteLookupConfig) > 0\n}","tryCatchPattern":null,"preventionTips":["Pipe service configs through 'jq empty' in CI to catch syntax errors.","Generate configs from typed templates rather than hand-editing JSON.","Unit-test the config blob by invoking rls.ParseConfig in a Go test before deploying."],"tags":["rls","service-config","json","validation","parse-config"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}