{"record":{"id":"ed7ed8bcbedffb9e","repo":"microsoft/aspire","slug":"aspire-merge-error-v","errorCode":null,"errorMessage":"aspire: merge error: %v","messagePattern":"aspire: merge error: (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/Aspire.Hosting.CodeGeneration.Go/Resources/base.go","lineNumber":879,"sourceCode":"\tif kind == reflect.Ptr {\n\t\tkind = v.Elem().Kind()\n\t}\n\n\tswitch kind {\n\tcase reflect.Map, reflect.Struct:\n\t\tdstMap, _ := toMap(dst)\n\t\tsrcMap, _ := toMap(src)\n\t\tmergedMap := merge(dstMap, srcMap, 0)\n\n\t\tbytes, _ := json.Marshal(mergedMap)\n\n\t\tvar result T\n\t\tif reflect.TypeOf(result).Kind() == reflect.Ptr {\n\t\t\tresult = reflect.New(reflect.TypeOf(result).Elem()).Interface().(T)\n\t\t}\n\n\t\tif err := json.Unmarshal(bytes, &result); err != nil {\n\t\t\tpanic(fmt.Sprintf(\"aspire: merge error: %v\", err))\n\t\t}\n\t\treturn result\n\tdefault:\n\t\treturn src\n\t}\n}\n\nfunc merge(dst, src map[string]any, depth int) map[string]any {\n\tconst depthLimit = 32\n\tif depth > depthLimit {\n\t\tpanic(\"aspire: deep update recursion limit of '32' exceeded\")\n\t}\n\n\tfor key, srcVal := range src {\n\t\tif dstVal, ok := dst[key]; ok {\n\t\t\tsrcSub, srcOk := toMap(srcVal)\n\t\t\tdstSub, dstOk := toMap(dstVal)\n","sourceCodeStart":861,"sourceCodeEnd":897,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Hosting.CodeGeneration.Go/Resources/base.go#L861-L897","documentation":"After a type check, deepUpdate marshals the merged value to JSON and unmarshals it into a fresh T. If json.Unmarshal fails (the JSON produced by marshal cannot be decoded into T — practically impossible for well-formed structs, but possible with custom marshalers or unsupported field shapes), the helper panics wrapping the unmarshal error.","triggerScenarios":"Calling a merge-style generated method where the value's MarshalJSON output cannot be unmarshaled into T — e.g. a type with a custom MarshalJSON emitting a shape incompatible with its own struct, or a field type UnmarshalJSON rejects.","commonSituations":"Custom JSON marshaler/unmarshaler asymmetry in user types merged via WithOptionalCallback/WithOptionalString-style APIs, embedded unsupported types (channels, funcs leaking through custom marshalers), or NaN in fields serialized through a custom path.","solutions":["Fix the type's UnmarshalJSON to accept its own MarshalJSON output","Remove custom marshaler asymmetry or unsupported field types from the struct being merged","Update the merge argument to a plain, JSON-round-trippable struct"],"exampleFix":"// before\nfunc (t T) MarshalJSON() ([]byte, error) { return []byte(`\"str\"`), nil }\n// UnmarshalJSON expects an object -> unmarshal fails\n// after\nfunc (t T) UnmarshalJSON(b []byte) error { /* accept the string form too */ ... }","handlingStrategy":"type-guard","validationCode":"// Verify the value survives a JSON round trip\nb, err := json.Marshal(v)\nif err == nil {\n\tvar probe T\n\tif err := json.Unmarshal(b, &probe); err != nil { /* incompatible marshal/unmarshal */ }\n}","typeGuard":"func jsonRoundTrips[T any](v T) bool {\n\tb, err := json.Marshal(v)\n\tif err != nil { return false }\n\tvar out T\n\treturn json.Unmarshal(b, &out) == nil\n}","tryCatchPattern":"defer func() {\n\tif r := recover(); r != nil {\n\t\treturn fmt.Errorf(\"merge failed: %v\", r)\n\t}\n}()","preventionTips":["Ensure custom MarshalJSON/UnmarshalJSON are symmetric","Avoid fields with channel/func types or NaN floats in merged structs","Unit-test merge methods with every option struct you use"],"tags":["go","json","merge","panic","unmarshal"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"25830f84bd145686607ad00c057b3f84e2e51d43","analyzedAt":"2026-09-16T11:10:06.193Z","contentChangedAt":"2026-09-16T11:10:06.193Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}