-
Notifications
You must be signed in to change notification settings - Fork 18.4k
Description
Proposal Details
Summary
Custom unmarshalers registered for underlying types (e.g., int
) should also apply to named types based on those underlying types (e.g., type Num int
). Currently, the custom unmarshaler is ignored when the target is a named type.
Go Version
go version go1.25 linux/amd64
What did you do?
I registered a custom unmarshaler for the int
type and expected it to also work when unmarshaling into a named type Num
that has int
as its underlying type.
package main
import (
"encoding/json/jsontext"
"encoding/json/v2"
"fmt"
)
type Num int
func main() {
var value Num
opt := json.WithUnmarshalers(json.UnmarshalFromFunc(func(decoder *jsontext.Decoder, t *int) error {
if err := decoder.SkipValue(); err != nil {
return err
}
*t = 2
return nil
}))
if err := json.Unmarshal([]byte(`1`), &value, opt); err != nil {
panic(err)
}
fmt.Println(value) // Expected: 2, Actual: 1
}
What did you expect to see?
Expected output: 2
The custom unmarshaler should be called because Num
has int
as its underlying type.
What did you see instead?
Actual output: 1
The custom unmarshaler is not called, and the default JSON unmarshaling behavior is used.
Why this matters
This limitation forces developers to:
- Enumerate all named types: Register separate unmarshalers for each named type, even when they share the same underlying type and logic.
// Current workaround - very verbose
opt := json.WithUnmarshalers(
json.UnmarshalFromFunc(customIntUnmarshaler[int]),
json.UnmarshalFromFunc(customIntUnmarshaler[Num]),
json.UnmarshalFromFunc(customIntUnmarshaler[UserID]),
json.UnmarshalFromFunc(customIntUnmarshaler[ProductID]),
// ... many more named int types
)
- Implement interfaces for every type: Add
UnmarshalJSONV2
method to each named type, duplicating logic.
func (n *Num) UnmarshalJSONV2(dec *jsontext.Decoder, options json.UnmarshalOptions) error {
return customIntLogic(dec, (*int)(n))
}
func (uid *UserID) UnmarshalJSONV2(dec *jsontext.Decoder, options json.UnmarshalOptions) error {
return customIntLogic(dec, (*int)(uid))
}
// ... repeat for every named type
Proposed Solution
The JSON v2 package should check for custom unmarshalers of the underlying type when no specific unmarshaler is found for a named type. The lookup order should be:
- Exact type match (current behavior)
- Underlying type match (proposed enhancement)
- Default behavior (current fallback)
This would align with Go's type system where named types can be converted to their underlying types.
Alternative Considered
While implementing UnmarshalJSONV2
on each type works, it creates maintenance burden and code duplication, especially in codebases with many similar named types (IDs, codes, measurements, etc.).
Benefits
- Reduced boilerplate: One unmarshaler can handle multiple related types
- Better maintainability: Changes to parsing logic only need to be made in one place
- Consistency with Go's type system: Named types are convertible to their underlying types
- Backward compatibility: Existing code continues to work unchanged
This enhancement would make JSON v2 more developer-friendly while maintaining type safety and performance.