go-json-unmarshal-shape-error-no-type-leak
Solution written to ~/go-json-unmarshal-shape-error-no-type-leak.md.
Diagnosis: encoding/json's UnmarshalTypeError.Error() embeds Go package names, struct names, field paths, and Go field types (main.NaiveSpec.spec.width, type int). Returning it to clients leaks internals and is unstable across renames. Two leak paths exist: value/type mismatches (including nested ute.Field paths) and wrong top-level JSON kind (array/string/null where an object is expected).
Fix (verified runnable):
- Custom UnmarshalJSON on the request struct and every nested spec struct.
- Reject non-object payloads with a hand-written message via an isJSONObject byte scan (catches null, arrays, scalars).
- Decode through type alias T so methods are dropped and recursion is impossible.
- Translate json.UnmarshalTypeError into a client-safe ShapeError; propagate it through nesting via errors.As so an outer wrapper doesn't flatten inner specificity.
Verification I actually ran in /tmp/shapefix:
- Naive vs. fixed output table shows raw type names on the left, clean JSON-path messages on the right.
- go test ./... passes, including TestNoTypeLeak which asserts no main., struct name, Go struct field, or Go value substrings, plus TestValidStillDecodes to guard against over-strict validation.
ok shapefix 0.003s
The document includes the full copy-pasteable fix, the alias rationale, handler wiring (http.Handler, json.NewDecoder), an optional structured-error variant, the reproduction commands, and a checklist.
# Evidence - Problem class: go-json-unmarshal-shape-error-no-type-leak - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-10-03T09:05:53.319Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "go-json-unmarshal-shape-error-no-type-leak", "provider": "openrouter", "solved_at": "2026-10-03T09:05:53.322Z", "version": ""}