{"record":{"id":"09116ac3c8155dd8","repo":"grpc/grpc-go","slug":"xds-unable-to-unmarshal-lbconfig-s-error-v","errorCode":null,"errorMessage":"xds: unable to unmarshal lbconfig: %s, error: %v","messagePattern":"xds: unable to unmarshal lbconfig: (.+?), error: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/xds/balancer/cdsbalancer/cdsbalancer.go","lineNumber":112,"sourceCode":"// Name returns the name of balancers built by this builder.\nfunc (bb) Name() string {\n\treturn cdsName\n}\n\n// lbConfig represents the loadBalancingConfig section of the service config\n// for the cdsBalancer.\ntype lbConfig struct {\n\tserviceconfig.LoadBalancingConfig\n\tClusterName string `json:\"cluster\"`\n\tIsDynamic   bool   `json:\"isDynamic\"`\n}\n\n// ParseConfig parses the JSON load balancer config provided into an\n// internal form or returns an error if the config is invalid.\nfunc (bb) ParseConfig(c json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {\n\tvar cfg lbConfig\n\tif err := json.Unmarshal(c, &cfg); err != nil {\n\t\treturn nil, fmt.Errorf(\"xds: unable to unmarshal lbconfig: %s, error: %v\", string(c), err)\n\t}\n\treturn &cfg, nil\n}\n\n// cdsBalancer implements a CDS based LB policy. It instantiates a\n// cluster_resolver balancer to further resolve the serviceName received from\n// CDS, into localities and endpoints. Implements the balancer.Balancer\n// interface which is exposed to gRPC and implements the balancer.ClientConn\n// interface which is exposed to the cluster_resolver balancer.\ntype cdsBalancer struct {\n\t// The following fields are initialized at build time and are either\n\t// read-only after that or provide their own synchronization, and therefore\n\t// do not need to be guarded by a mutex.\n\tcc                balancer.ClientConn   // ClientConn interface passed to child LB.\n\tbOpts             balancer.BuildOptions // BuildOptions passed to child LB.\n\tchildConfigParser balancer.ConfigParser // Config parser for cluster_resolver LB policy.\n\tlogger            *grpclog.PrefixLogger // Prefix logger for all logging.\n","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/internal/xds/balancer/cdsbalancer/cdsbalancer.go#L94-L130","documentation":"Returned by bb.ParseConfig (line 112) when json.Unmarshal fails on the raw JSON load balancer config passed to the CDS balancer's config parser. The %s shows the raw JSON input and %v shows the unmarshalling error. This config is the {\"cluster\": \"...\", \"isDynamic\": ...} structure defined by the lbConfig struct at lines 101-105.","triggerScenarios":"The xDS resolver (or a test harness) calls the cds_experimental balancer's ParseConfig with malformed JSON, or JSON whose 'cluster'/'isDynamic' fields have wrong types. In production this is generated by the xdsResolver so should be well-formed; in tests or custom resolvers it can be passed bad data.","commonSituations":"A custom service config or test that constructs the CDS LB config JSON by hand with a syntax error or type mismatch (e.g. isDynamic as a string instead of bool). A version mismatch where the lbConfig struct fields changed but the caller sends old-format JSON.","solutions":["Inspect the full error message which includes the raw JSON (%s) — the malformed portion is usually visible there.","Validate that the JSON matches {\"cluster\": \"<string>\", \"isDynamic\": <bool>} exactly.","Do not hand-construct CDS balancer config; rely on the xDS resolver to generate it.","In tests, use json.Marshal on a properly constructed lbConfig struct rather than string concatenation."],"exampleFix":"// before: hand-built JSON with a typo\nraw := []byte(`{\"cluster\":\"my-cluster\",\"isDyanmic\":true}`) // 'isDyanmic' misspelled\n\n// after: construct via struct + json.Marshal\ncfg := lbConfig{ClusterName: \"my-cluster\", IsDynamic: true}\nraw, _ := json.Marshal(cfg)","handlingStrategy":"validation","validationCode":"// Validate CDS LB config JSON before passing it to ParseConfig\nfunc validateCDSConfig(raw json.RawMessage) error {\n    var cfg struct {\n        ClusterName string `json:\"cluster\"`\n        IsDynamic   bool   `json:\"isDynamic\"`\n    }\n    if err := json.Unmarshal(raw, &cfg); err != nil {\n        return fmt.Errorf(\"invalid CDS config JSON: %w\", err)\n    }\n    if cfg.ClusterName == \"\" {\n        return fmt.Errorf(\"CDS config missing 'cluster' field\")\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"// Go: check the error from ParseConfig\ncfg, err := cdsBuilder.ParseConfig(rawJSON)\nif err != nil {\n    if strings.Contains(err.Error(), \"unable to unmarshal lbconfig\") {\n        log.Printf(\"CDS config JSON is invalid: %s\", rawJSON)\n        // fix the JSON source\n    }\n}","preventionTips":["Never hand-construct balancer config JSON; always use json.Marshal on typed structs.","In tests, validate the JSON with a schema check before passing to ParseConfig.","Log the raw JSON on parse failure for easier debugging."],"tags":["xds","config","json","parse","cds"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}