Repository navigation
encoding/json: Marshal now honors MarshalText for string-kind map keys #81355
Description
Go version
go version go1.27.1 darwin/arm64
Output of go env in your module/workspace:
AR='ar'
CC='clang'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='clang++'
GCCGO='gccgo'
GO111MODULE=''
GOARCH='arm64'
GOARM64='v8.0'
GOAUTH='netrc'
GOBIN='/Users/foo/go/bin'
GOCACHE='/Users/foo/Library/Caches/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/Users/foo/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/16/kzw60w_j0d51pqty3l1bywhm0000gp/T/go-build2885541076=/tmp/go-build -gno-record-gcc-switches -fno-common'
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMOD='/dev/null'
GOMODCACHE='/Users/foo/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPACKAGESDRIVER=''
GOPATH='/Users/foo/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/Users/foo/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.27.1.darwin-arm64'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/Users/foo/Library/Application Support/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='go1.27.1+auto'
GOTOOLDIR='/Users/foo/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.27.1.darwin-arm64/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.27.1'
GOWORK=''
PKG_CONFIG='pkg-config'What did you do?
json.Marshal calls MarshalText on map keys now even when the key's underlying type is a string, which doesn't match what the docs say and doesn't match 1.26.
Playground: https://go.dev/play/p/PB6ZpeHcnuE
Flip between go versions in the playground above
The doc comment on Marshal (https://pkg.go.dev/encoding/json#Marshal) says map keys of any string type are used directly, and that TextMarshaler only applies otherwise:
Map values encode as JSON objects. The map's key type must either be a
string, an integer type, or implement encoding.TextMarshaler. The map keys
are sorted and used as JSON object keys by applying the following rules:
- keys of any string type are used directly
- keys that implement encoding.TextMarshaler are marshaled
- integer keys are converted to strings
Guessing this is a side effect of the json package being reimplemented on top of json/v2 internally, but if it's intentional the doc comment needs to be updated, since it currently describes behavior that isn't true anymore.
What did you see happen?
Different marshaling of keys depending on golang version 1.26 vs 1.27
What did you expect to see?
Consistent marshaling between the two versions
Activity
gabyhelp commentedon Sep 4, 2026
Related Issues
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)
prattmic commentedon Sep 4, 2026
cc @dsnet @neild @ChrisHines
dsnet commentedon Sep 4, 2026
Thank you for the report. Wow... So
MarshalTextis NOT used, butUnmarshalTextIS used. Surprisingly inconsistency.Yes, this is a result of switching to the v2 implementation, which consistently calls method implementations. We'll fix this regression.
gopherbot commentedon Sep 4, 2026
Change https://go.dev/cl/827864 mentions this issue:
encoding/json: avoid calling MarshalText on named string map keysdsnet commentedon Sep 4, 2026
@gopherbot Please backport to 1.27. This is a regression where MarshalText is called on map keys of string kind, when it previously it did not in Go 1.26.
gopherbot commentedon Sep 4, 2026
Backport issue(s) opened:
#81361 (for 1.26), #81362 (for 1.27).Remember to create the cherry-pick CL(s) as soon as the patch is submitted to master, according to https://go.dev/wiki/MinorReleases.
encoding/json: avoid calling MarshalText on named string map keys
gopherbot commentedon Sep 15, 2026
Change https://go.dev/cl/832764 mentions this issue:
[release-branch.go1.27] encoding/json: avoid calling MarshalText on named string map keys[release-branch.go1.27] encoding/json: avoid calling MarshalText on n…
0xff-dev commentedon Oct 3, 2026
The fix for this issue (CL https://go.dev/cl/827864) also stopped calling MarshalText on map keys of pointer or interface kind that refer to a string kind, which differs from Go 1.26:
Since CL 827864 was backported to Go 1.27, this would ship in go1.27.2 as well.
Fix: https://go.dev/cl/843805
gopherbot commentedon Oct 3, 2026
Change https://go.dev/cl/843805 mentions this issue:
encoding/json: call MarshalText on map keys not of string kinddsnet commentedon Oct 9, 2026
@gopherbot backport CL/843805 to Go 1.27 as it fixes a regression with how MarshalText is handled on a pointer receiver type used as a map key.
encoding/json: call MarshalText on map keys not of string kind
jtorvald commentedon Oct 28, 2026
The second example in #81355 (comment) is still broken on tip after CL 843805. Only the pointer case was fixed:
The test added in CL 843805 only puts pointers into the interface, never a string-kind value. The interface marshaler copies the value into a new addressable value, so the method marshaler treats it like a string-kind map key and skips
MarshalText. Errors returned byMarshalTextare also silently dropped in this case.Fix: https://go.dev/cl/855441. It decides "use the string as-is" in the map marshaler based on the map's key type (as Go 1.26 did), instead of in the method marshalers.
gopherbot commentedon Oct 28, 2026
Change https://go.dev/cl/855441 mentions this issue:
encoding/json: call MarshalText on string-kind map keys in an interface