grpc/grpc-go · error

rls: json unmarshal failed for service config %+v

Error message

rls: json unmarshal failed for service config %+v: %v

What it means

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.

Solutions

  1. Run the config through a JSON validator (e.g. 'jq empty cfg.json') to surface syntax errors.
  2. Confirm the top-level shape matches lbConfigJSON: an object with RouteLookupConfig (object), optional RouteLookupChannelServiceConfig (object), ChildPolicy (array of single-key objects), and ChildPolicyConfigTargetFieldName (string).
  3. 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.
  4. Regenerate the config from a known-good template or the RLS e2e test fixtures under balancer/rls/internal/test/e2e.

Example fix

// before: trailing comma / type mismatch
{
  "loadBalancingConfig": [ { "rls": { "routeLookupConfig": { ... }, } } ]
}

// after: valid JSON, correct field types
{
  "loadBalancingConfig": [
    { "rls": {
        "routeLookupConfig": { ... },
        "childPolicyConfigTargetFieldName": "serviceName",
        "childPolicy": [ { "round_robin": {} } ]
    } }
  ]
}
Defensive patterns

Strategy: validation

Validate before calling

// Pre-validate that the RLS LB config unmarshals into the expected shape.
type lbConfigJSON struct {
    RouteLookupConfig                json.RawMessage
    RouteLookupChannelServiceConfig  json.RawMessage
    ChildPolicy                      []map[string]json.RawMessage
    ChildPolicyConfigTargetFieldName string
}
func validateRLSConfig(raw []byte) error {
    var c lbConfigJSON
    if err := json.Unmarshal(raw, &c); err != nil {
        return fmt.Errorf("invalid RLS service config JSON: %w", err)
    }
    if len(c.RouteLookupConfig) == 0 {
        return errors.New("routeLookupConfig is required")
    }
    return nil
}

Type guard

func isParsableRLSConfig(raw []byte) bool {
    var c struct{ RouteLookupConfig json.RawMessage }
    return json.Unmarshal(raw, &c) == nil && len(c.RouteLookupConfig) > 0
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/5ae15a295950568e. Report an issue: GitHub.

Appendix: source

Thrown at balancer/rls/config.go:152

//	   - `cache_size_bytes` field must have a value greater than 0, and if its
//	     value is greater than 5M, we cap it at 5M
//
//	- routeLookupChannelServiceConfig:
//	  - if specified, must parse as valid service config
//
//	- childPolicy:
//	  - must find a valid child policy with a valid config
//
//	- childPolicyConfigTargetFieldName:
//	  - must be set and non-empty
func (rlsBB) ParseConfig(c json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
	if logger.V(2) {
		logger.Infof("Received JSON service config: %v", pretty.ToJSON(c))
	}

	cfgJSON := &lbConfigJSON{}
	if err := json.Unmarshal(c, cfgJSON); err != nil {
		return nil, fmt.Errorf("rls: json unmarshal failed for service config %+v: %v", string(c), err)
	}

	m := protojson.UnmarshalOptions{DiscardUnknown: true}
	rlsProto := &rlspb.RouteLookupConfig{}
	if err := m.Unmarshal(cfgJSON.RouteLookupConfig, rlsProto); err != nil {
		return nil, fmt.Errorf("rls: bad RouteLookupConfig proto %+v: %v", string(cfgJSON.RouteLookupConfig), err)
	}
	lbCfg, err := parseRLSProto(rlsProto)
	if err != nil {
		return nil, err
	}

	if sc := string(cfgJSON.RouteLookupChannelServiceConfig); sc != "" {
		parsed := internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(sc)
		if parsed.Err != nil {
			return nil, fmt.Errorf("rls: bad control channel service config %q: %v", sc, parsed.Err)
		}
		lbCfg.controlChannelServiceConfig = sc

View on GitHub (pinned to 0c51461d27)