{"record":{"id":"02b756ef38556508","repo":"go-kratos/kratos","slug":"config-merge-dst-must-be-map-string-interface","errorCode":null,"errorMessage":"config: merge dst must be *map[string]interface{}, got %T","messagePattern":"config: merge dst must be \\*map\\[string\\]interface(.+?), got %T","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"config/merge.go","lineNumber":8,"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","sourceCodeStart":1,"sourceCodeEnd":26,"githubUrl":"https://github.com/go-kratos/kratos/blob/668db92c2c001e9552594ba5a8aede8456af6d7e/config/merge.go#L1-L26","documentation":"config.defaultMerge requires the merge destination to be *map[string]interface{} so it can allocate a nil destination map in place; any other dynamic type fails this check. In practice it appears when a custom Merger (registered via the config merger option) forwards a wrongly-typed dst to the default merger, or internal merge APIs are called directly.","triggerScenarios":"defaultMerge invoked with dst of any type other than *map[string]any — e.g. a custom Merger passing a map by value, or an unrelated type, instead of a pointer to the map.","commonSituations":"Writing a custom merger to customize deep-merge behavior and delegating to the default with a reshaped dst; code copied from tests that call merge internals directly.","solutions":["Pass the destination as *map[string]any (pointer), never a map value","If you need different semantics, register a full custom Merger instead of partially reusing defaultMerge","Convert unknown-shaped values with the package's convertMap before merging"],"exampleFix":"// before\nvar dst map[string]any\n_ = defaultMerge(dst, src) // passed by value: merge dst must be *map[string]interface{}\n\n// after\nvar dst map[string]any\n_ = defaultMerge(&dst, src)","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func isMergeableDst(dst any) bool {\n    _, ok := dst.(*map[string]any)\n    return ok\n}","tryCatchPattern":"if err := defaultMerge(dst, src); err != nil {\n    if strings.HasPrefix(err.Error(), \"config: merge dst\") {\n        dstMap := map[string]any{}\n        err = defaultMerge(&dstMap, src) // correct shape, retry once\n    }\n    if err != nil {\n        return err\n    }\n}","preventionTips":["Always pass *map[string]any (pointer) as the merge destination","Prefer the package's Merger option over hand-rolled merging","Unit-test custom mergers with nil destination maps"],"tags":["config","type-assertion","merge","go"],"backgroundTag":null,"analyzedSha":"668db92c2c001e9552594ba5a8aede8456af6d7e","analyzedAt":"2026-08-16T02:07:20.704Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}