grpc/grpc-go · error

rls: json unmarshal failed for child policy config

Error message

rls: json unmarshal failed for child policy config %q: %v

What it means

Emitted from parseChildPolicyConfigs (config.go:310) when json.Unmarshal of a child policy's raw config into map[string]json.RawMessage fails. The child config must be a JSON object so the RLS balancer can inject the target field; a non-object (array, string, number) or syntactically invalid JSON triggers this.

Solutions

  1. Make each child policy config a JSON object, even if empty: {"round_robin":{}}.
  2. Run the full config through 'jq' to confirm every child config parses as an object.
  3. If the child policy needs parameters, provide them as an object (e.g. {"weighted_round_robin":{"weightedTargets":{...}}}).

Example fix

// before
"childPolicy": [ { "round_robin": "" } ]

// after
"childPolicy": [ { "round_robin": {} } ]
Defensive patterns

Strategy: validation

Validate before calling

func validateChildConfigIsObject(raw json.RawMessage) error {
    var m map[string]json.RawMessage
    if err := json.Unmarshal(raw, &m); err != nil {
        return fmt.Errorf("child policy config is not a JSON object: %w", err)
    }
    return nil
}

Type guard

func childConfigIsJSONObject(raw json.RawMessage) bool {
    var m map[string]json.RawMessage
    return json.Unmarshal(raw, &m) == nil
}

Prevention

When it happens

Trigger: A childPolicy config value that is JSON-valid but not an object (e.g. "round_robin": []), or syntactically invalid JSON bytes inside an otherwise-valid outer config.

Common situations: Setting "round_robin": "" or "round_robin": null; a config generator that emits the config as a JSON string instead of an object; trailing commas inside the child config.

Related errors


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

Appendix: source

Thrown at balancer/rls/config.go:310

		for name, rawCfg = range config {
		}
		builder := balancer.Get(name)
		if builder == nil {
			continue
		}
		parser, ok := builder.(balancer.ConfigParser)
		if !ok {
			return "", nil, fmt.Errorf("rls: childPolicy %q with config %q does not support config parsing", name, string(rawCfg))
		}

		// To validate child policy configs we do the following:
		// - unmarshal the raw JSON bytes of the child policy config into a map
		// - add an entry with key set to `target_field_name` and a dummy value
		// - marshal the map back to JSON and parse the config using the parser
		// retrieved previously
		var childConfig map[string]json.RawMessage
		if err := json.Unmarshal(rawCfg, &childConfig); err != nil {
			return "", nil, fmt.Errorf("rls: json unmarshal failed for child policy config %q: %v", string(rawCfg), err)
		}
		childConfig[targetFieldName], _ = json.Marshal(dummyChildPolicyTarget)
		jsonCfg, err := json.Marshal(childConfig)
		if err != nil {
			return "", nil, fmt.Errorf("rls: json marshal failed for child policy config {%+v}: %v", childConfig, err)
		}
		if _, err := parser.ParseConfig(jsonCfg); err != nil {
			return "", nil, fmt.Errorf("rls: childPolicy config validation failed: %v", err)
		}
		return name, childConfig, nil
	}
	return "", nil, fmt.Errorf("rls: invalid childPolicy config: no supported policies found in %+v", childPolicies)
}

func convertDuration(d *durationpb.Duration) (time.Duration, error) {
	if d == nil {
		return 0, nil
	}

View on GitHub (pinned to 0c51461d27)