grpc/grpc-go · error

expected a JSON struct with one entry; received entry %v at

Error message

expected a JSON struct with one entry; received entry %v at index %d

What it means

Returned by gracefulswitch.ParseConfig when unmarshalling the loadBalancingConfig JSON array. Each array element must be a JSON object with exactly one key (the LB policy name). If an element has zero keys (empty object) or two or more keys, the parser rejects it with this error. The gracefulswitch balancer uses this format to identify which child policy to activate.

Source

Thrown at internal/balancer/gracefulswitch/config.go:56

func ChildName(l serviceconfig.LoadBalancingConfig) string {
	return l.(*lbConfig).childBuilder.Name()
}

// ParseConfig parses a child config list and returns a LB config for the
// gracefulswitch Balancer.
//
// cfg is expected to be a json.RawMessage containing a JSON array of LB policy
// names + configs as the format of the "loadBalancingConfig" field in
// ServiceConfig.  It returns a type that should be passed to
// UpdateClientConnState in the BalancerConfig field.
func ParseConfig(cfg json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
	var lbCfg []map[string]json.RawMessage
	if err := json.Unmarshal(cfg, &lbCfg); err != nil {
		return nil, err
	}
	for i, e := range lbCfg {
		if len(e) != 1 {
			return nil, fmt.Errorf("expected a JSON struct with one entry; received entry %v at index %d", e, i)
		}

		var name string
		var jsonCfg json.RawMessage
		for name, jsonCfg = range e {
		}

		builder := balancer.Get(name)
		if builder == nil {
			// Skip unregistered balancer names.
			continue
		}

		parser, ok := builder.(balancer.ConfigParser)
		if !ok {
			// This is a valid child with no config.
			return &lbConfig{childBuilder: builder}, nil
		}

View on GitHub (pinned to 0c51461d27)

Solutions

  1. Ensure each element of the loadBalancingConfig JSON array contains exactly one key mapping a policy name to its config object
  2. Split multi-key entries into separate array elements: [{"a":{}},{"b":{}}] instead of [{"a":{},"b":{}}]
  3. Validate the JSON structure by unmarshalling into []map[string]json.RawMessage and asserting len(entry)==1 before calling ParseConfig

Example fix

// before (malformed: two keys in one entry)
"loadBalancingConfig": [{"round_robin":{}, "weighted_target":{}}]

// after (correct: one key per entry)
"loadBalancingConfig": [{"round_robin":{}}, {"weighted_target":{}}]
Defensive patterns

Strategy: validation

Validate before calling

// Validate loadBalancingConfig JSON before calling gracefulswitch.ParseConfig
func validateLBConfig(raw json.RawMessage) error {
    var entries []map[string]json.RawMessage
    if err := json.Unmarshal(raw, &entries); err != nil {
        return fmt.Errorf("not a JSON array: %w", err)
    }
    for i, e := range entries {
        if len(e) != 1 {
            return fmt.Errorf("entry at index %d has %d keys; expected exactly 1", i, len(e))
        }
    }
    return nil
}

Prevention

When it happens

Trigger: Calling gracefulswitch.ParseConfig() or balancergroup.ParseConfig() (which delegates to it at balancergroup.go:606) with a json.RawMessage whose parsed array contains an entry that is not a single-key map. For example, [{"round_robin":{},"grpclb":{}}] puts two policies in one entry, or [{}] has zero keys.

Common situations: A hand-edited or programmatically-generated service config JSON where multiple LB policies are placed inside a single array element instead of being split across separate elements. This commonly occurs when developers unfamiliar with the gRPC loadBalancingConfig format try to list fallback policies.

Related errors


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