{"record":{"id":"6d4131e72f629f9f","repo":"nats-io/nats-server","slug":"can-not-unmarshal-q","errorCode":null,"errorMessage":"can not unmarshal %q","messagePattern":"can not unmarshal %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/store.go","lineNumber":594,"sourceCode":"\tcase InterestPolicy:\n\t\treturn interestPolicyJSONBytes, nil\n\tcase WorkQueuePolicy:\n\t\treturn workQueuePolicyJSONBytes, nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"can not marshal %v\", rp)\n\t}\n}\n\nfunc (rp *RetentionPolicy) UnmarshalJSON(data []byte) error {\n\tswitch string(data) {\n\tcase limitsPolicyJSONString:\n\t\t*rp = LimitsPolicy\n\tcase interestPolicyJSONString:\n\t\t*rp = InterestPolicy\n\tcase workQueuePolicyJSONString:\n\t\t*rp = WorkQueuePolicy\n\tdefault:\n\t\treturn fmt.Errorf(\"can not unmarshal %q\", data)\n\t}\n\treturn nil\n}\n\nfunc (dp DiscardPolicy) String() string {\n\tswitch dp {\n\tcase DiscardOld:\n\t\treturn \"DiscardOld\"\n\tcase DiscardNew:\n\t\treturn \"DiscardNew\"\n\tdefault:\n\t\treturn \"Unknown Discard Policy\"\n\t}\n}\n\nfunc (dp DiscardPolicy) MarshalJSON() ([]byte, error) {\n\tswitch dp {\n\tcase DiscardOld:","sourceCodeStart":576,"sourceCodeEnd":612,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/store.go#L576-L612","documentation":"RetentionPolicy.UnmarshalJSON accepts only the exact JSON strings for LimitsPolicy, InterestPolicy, and WorkQueuePolicy (case-sensitive, exact match on the raw bytes). Any other JSON input, including misspellings or different casing, hits the default branch and returns this error.","triggerScenarios":"json.Unmarshal (or loading a channel config file / obtaining a Channel via the store) where the retention field contains a value other than \"limits\", \"interest\", or \"workqueue\" exactly.","commonSituations":"Hand-edited or generated stan.conf with e.g. \"Limits\" or \"limit\" instead of \"limits\"; config written by a different/older server version using other tokens; programmatically unmarshaling user-supplied JSON.","solutions":["Correct the JSON value to one of the exact strings: \"limits\", \"interest\", or \"workqueue\" (lowercase, quoted).","Pre-validate incoming JSON against the allowed values before unmarshaling.","Regenerate the config file using a matching version of nats-streaming-server."],"exampleFix":"// before\nvar rp stan.RetentionPolicy\njson.Unmarshal([]byte(`\"limits_policy\"`), &rp) // error\n// after\njson.Unmarshal([]byte(`\"limits\"`), &rp) // ok","handlingStrategy":"validation","validationCode":"var allowedRetention = map[string]bool{\"\\\"limits\\\"\": true, \"\\\"interest\\\"\": true, \"\\\"workqueue\\\"\": true}\nif !allowedRetention[string(data)] {\n\treturn fmt.Errorf(\"invalid retention policy %s\", data)\n}","typeGuard":"func parseRetentionPolicy(data []byte) (stan.RetentionPolicy, bool) {\n\tvar rp stan.RetentionPolicy\n\tif err := rp.UnmarshalJSON(data); err != nil {\n\t\treturn 0, false\n\t}\n\treturn rp, true\n}","tryCatchPattern":"if err := json.Unmarshal(data, &rp); err != nil {\n\tif strings.Contains(err.Error(), \"can not unmarshal\") {\n\t\tlog.Printf(\"unknown retention policy %q, defaulting to limits\", data)\n\t\trp = stan.LimitsPolicy\n\t\treturn nil\n\t}\n\treturn err\n}","preventionTips":["Keep config files lowercase and quoted exactly as the library constants define.","Never hand-edit persisted state; use the server's own tooling.","Add schema validation on any externally supplied JSON before unmarshaling."],"tags":["go","json","deserialization","nats-streaming"],"backgroundTag":"json-unmarshal-invalid-enum","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}