Go version
go1.25
Output of go env
in your module/workspace:
n/a
What did you do?
package main
import (
jsonv1 "encoding/json"
"fmt"
"net/netip"
jsonv1in2 "github.com/go-json-experiment/json/v1"
"tailscale.com/util/must"
)
type HostValue struct {
netip.Prefix
}
func (p HostValue) MarshalText() ([]byte, error) {
if p.Prefix.IsSingleIP() {
return p.Prefix.Addr().MarshalText()
}
return p.Prefix.MarshalText()
}
func main() {
m := map[string]HostValue{
"example-host-1": HostValue{must.Get(netip.ParsePrefix("100.100.100.100/32"))},
}
fmt.Println(string(must.Get(jsonv1.Marshal(&m))))
fmt.Println(string(must.Get(jsonv1in2.Marshal(&m))))
}
What did you see happen?
{"example-host-1":"100.100.100.100"}
{"example-host-1":"100.100.100.100/32"}
What did you expect to see?
{"example-host-1":"100.100.100.100"}
{"example-host-1":"100.100.100.100"}
It seems that the MarshalText
method was not called by the v1 emulation in v2.
Comment From: dsnet
It turns out that this is just a fundamental risk with Go embedding.
type HostValue struct {
netip.Prefix // forwards both `MarshalText` and `AppendText`
}
func (p HostValue) MarshalText() ([]byte, error) // tries to override MarshalText, but fails to override AppendText
The problem is that the new v1 emulation in v2 respects the new encoding.TextAppender
interface, while the v1 implementation does not.
I wonder if this should be a static analysis check that flags the following pattern: * type T embeds type B, which has both B.MarshalXXX, and B.AppendXXX * type T declares T.MarshalXXX, indicating intent to override B.MarshalXXX, but fails to also implement T.AppendXXX
\cc @neild @adonovan
Comment From: gabyhelp
Related Issues
- encoding/json: Marshal of nil net.IP fails #6339 (closed)
- net/netip: Prefix's MarshalText and String methods produce different values #50115 (closed)
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)
Comment From: dsnet
I'm renaming this since this isn't an encoding/json/v2 issue.
I propose the following static check:
* If T
embeds E
and
* E
implements special methods M1
and M2
(where both M1
and M2
are semantically equivalent and both understood by common packages; for example MarshalText
and AppendText
are both understood by encoding/json/v2
) and
* T
only explicitly implements one of the methods (e.g., just M1
or M2
) in the set,
* then report a violation.
Examples of magic methods which should be semantically equivalent:
* having both MarshalText
and AppendText
* having both MarshalBinary
and AppendBinary
* having both MarshalJSON
and MarshalJSONTo
* having both UnmarshalJSON
and UnmarshalJSONFrom
* having both Read
and WriteTo
* having both Write
and ReadFrom
I'm sure there are other sets of methods, but this comes to mind off the top of my head.
\cc @dominikh
Comment From: gopherbot
Change https://go.dev/cl/704375 mentions this issue: gopls/internal/analysis/methodembed: detect inconsistent overriding