{"record":{"id":"7df9d5c05782aa70","repo":"go-kratos/kratos","slug":"config-merge-src-must-be-map-string-interface","errorCode":null,"errorMessage":"config: merge src must be map[string]interface{}, got %T","messagePattern":"config: merge src must be map\\[string\\]interface(.+?), got %T","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"config/merge.go","lineNumber":12,"sourceCode":"package config\n\nimport \"fmt\"\n\nfunc defaultMerge(dst, src any) error {\n\tdstMap, ok := dst.(*map[string]any)\n\tif !ok {\n\t\treturn fmt.Errorf(\"config: merge dst must be *map[string]interface{}, got %T\", dst)\n\t}\n\tsrcMap, ok := convertMap(src).(map[string]any)\n\tif !ok {\n\t\treturn fmt.Errorf(\"config: merge src must be map[string]interface{}, got %T\", src)\n\t}\n\tif *dstMap == nil {\n\t\t*dstMap = make(map[string]any, len(srcMap))\n\t}\n\tmergeMap(*dstMap, srcMap)\n\treturn nil\n}\n\nfunc mergeMap(dst, src map[string]any) {\n\tfor key, srcValue := range src {\n\t\tif srcMap, ok := srcValue.(map[string]any); ok {\n\t\t\tif dstMap, ok := dst[key].(map[string]any); ok {\n\t\t\t\tmergeMap(dstMap, srcMap)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tdst[key] = cloneMergeValue(srcValue)\n\t}","sourceCodeStart":1,"sourceCodeEnd":30,"githubUrl":"https://github.com/go-kratos/kratos/blob/668db92c2c001e9552594ba5a8aede8456af6d7e/config/merge.go#L1-L30","documentation":"The default config merger converts src via convertMap and requires the result to be map[string]interface{}; values that decode to slices, scalars, or other shapes fail this check. The root cause is almost always a config document whose top level is not a mapping (e.g. a YAML list or JSON array).","triggerScenarios":"A Source returns bytes that decode to a non-map root — YAML starting with a list item, a JSON array, or a bare scalar — so convertMap(src) cannot produce map[string]any during merge.","commonSituations":"A docker-compose-style YAML fragment reused as config; concatenated YAML documents; a values file that is a single scalar; hand-written JSON arrays at the root.","solutions":["Edit the config file so its top level is a mapping (YAML map / JSON object)","Move lists under a named key: items: [ ... ]","Validate locally before shipping: yaml.Unmarshal(file, &map[string]any) must succeed"],"exampleFix":"# before - config.yaml (top-level list)\n- name: a\n  port: 8000\n- name: b\n  port: 8001\n\n# after\nservices:\n  - name: a\n    port: 8000\n  - name: b\n    port: 8001","handlingStrategy":"type-guard","validationCode":"var probe any\nif err := codec.Unmarshal(raw, &probe); err != nil {\n    return err\n}\nif _, ok := probe.(map[string]any); !ok {\n    return fmt.Errorf(\"config root must be a mapping, got %T\", probe)\n}","typeGuard":"func isMapConfig(v any) bool {\n    switch v.(type) {\n    case map[string]any, map[any]any:\n        return true\n    }\n    return false\n}","tryCatchPattern":null,"preventionTips":["Keep every config file's top level a mapping","Put lists under a named key, never at the root","Validate config files by decoding into map[string]any in CI"],"tags":["config","yaml","json","merge","validation"],"backgroundTag":null,"analyzedSha":"668db92c2c001e9552594ba5a8aede8456af6d7e","analyzedAt":"2026-08-16T02:07:20.704Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}