{"record":{"id":"dab6741635f9f521","repo":"alibaba/open-code-review","slug":"expected-in-path-rule-map-got-v","errorCode":null,"errorMessage":"expected '{' in path_rule_map, got %v","messagePattern":"expected '\\{' in path_rule_map, got (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/config/rules/system_rules.go","lineNumber":67,"sourceCode":"\t// Use json.Decoder with UseNumber to preserve order of path_rule_map keys.\n\tvar raw map[string]json.RawMessage\n\tif err := json.Unmarshal(data, &raw); err != nil {\n\t\treturn err\n\t}\n\tmapData, ok := raw[\"path_rule_map\"]\n\tif !ok || len(mapData) == 0 || string(mapData) == \"null\" {\n\t\treturn nil\n\t}\n\n\t// Parse ordered keys using a streaming decoder.\n\tdec := json.NewDecoder(strings.NewReader(string(mapData)))\n\t// Read opening '{'\n\tt, err := dec.Token()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"expected '{' in path_rule_map: %w\", err)\n\t}\n\tif t != json.Delim('{') {\n\t\treturn fmt.Errorf(\"expected '{' in path_rule_map, got %v\", t)\n\t}\n\tfor dec.More() {\n\t\t// Read key\n\t\tkeyTok, err := dec.Token()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"read path_rule_map key: %w\", err)\n\t\t}\n\t\tkey, ok := keyTok.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"expected string key in path_rule_map, got %T\", keyTok)\n\t\t}\n\t\t// Read value\n\t\tvar value string\n\t\tif err := dec.Decode(&value); err != nil {\n\t\t\treturn fmt.Errorf(\"read path_rule_map value for %q: %w\", key, err)\n\t\t}\n\t\tr.PathRules = append(r.PathRules, PathRule{Pattern: key, Rule: value})\n\t}","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/alibaba/open-code-review/blob/5cf97d0d15cbd41b602513c4be3bfec3cee5bf7f/internal/config/rules/system_rules.go#L49-L85","documentation":"SystemRule.UnmarshalJSON read the first token of path_rule_map successfully but it was not json.Delim('{') — the value is valid JSON yet not an object (e.g. an array, string, or number). The ordered-key streaming parser requires an object because it maps keys (glob patterns) to rule names in encounter order.","triggerScenarios":"path_rule_map set to a JSON array, string, number, or boolean in the rules config — e.g. \"path_rule_map\": [\"*.go\"] or \"path_rule_map\": \"golang.md\" — then the config is unmarshalled into SystemRule.","commonSituations":"Schema confusion after changing config format between versions; hand-written config using a list of objects instead of a map; YAML/JSON conversion tools that turned a map into a list of pairs.","solutions":["Change path_rule_map to a JSON object: {\"<glob pattern>\": \"<rule file>\", ...}","Convert list-of-pairs form [{pattern, rule}] into a map form","Check the config against the expected SystemRule schema for your version","Run 'jq .path_rule_map' on the config — the output must be an object"],"exampleFix":"// before\n\"path_rule_map\": [{\"pattern\": \"*.go\", \"rule\": \"golang.md\"}]\n// after\n\"path_rule_map\": {\"*.go\": \"golang.md\"}","handlingStrategy":"validation","validationCode":"func assertPathRuleMapObject(data []byte) error {\n    var probe struct {\n        PathRuleMap json.RawMessage `json:\"path_rule_map\"`\n    }\n    if err := json.Unmarshal(data, &probe); err != nil { return err }\n    if len(probe.PathRuleMap) == 0 || string(probe.PathRuleMap) == \"null\" { return nil }\n    var obj map[string]string\n    if err := json.Unmarshal(probe.PathRuleMap, &obj); err != nil {\n        return fmt.Errorf(\"path_rule_map must be an object: %w\", err)\n    }\n    return nil\n}","typeGuard":"func isJSONObject(v json.RawMessage) bool {\n    return len(v) > 0 && strings.HasPrefix(strings.TrimSpace(string(v)), \"{\")\n}","tryCatchPattern":"if err := json.Unmarshal(cfgData, &systemRule); err != nil {\n    if strings.Contains(err.Error(), \"expected '{' in path_rule_map\") {\n        return fmt.Errorf(\"path_rule_map must be an object of {glob: ruleFile}: %w\", err)\n    }\n    return err\n}","preventionTips":["Document and enforce the schema: path_rule_map is {\"<glob>\": \"<rule file>\"}","Convert legacy list-of-pairs configs to map form during config migration","Validate with 'jq -e '.path_rule_map | type == \"object\"'","Add a schema check to CI for custom rules files"],"tags":["config","json-parsing","rules","schema-mismatch"],"backgroundTag":"invalid-json-config","analyzedSha":"5cf97d0d15cbd41b602513c4be3bfec3cee5bf7f","analyzedAt":"2026-09-02T02:08:09.116Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}