From cecaa65d4fe5d9a3a4f164737f0cf2093eac1a20 Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Wed, 25 Feb 2026 19:37:31 +0100 Subject: [PATCH] JSON: Support integer map keys Now that stdlib will marshal integers as JSON we should support the same. --- msgp/json.go | 28 ++++++++++++++++++++++++++-- msgp/json_bytes.go | 21 +++++++++++++++++++++ msgp/json_bytes_test.go | 23 +++++++++++++++++++++++ msgp/json_test.go | 23 +++++++++++++++++++++++ 4 files changed, 93 insertions(+), 2 deletions(-) diff --git a/msgp/json.go b/msgp/json.go index 896d690b..49bd54bf 100644 --- a/msgp/json.go +++ b/msgp/json.go @@ -131,11 +131,35 @@ func rwMap(dst jsWriter, src *Reader) (n int, err error) { n++ } - field, err = src.ReadMapKeyPtr() + var kt Type + kt, err = src.NextType() if err != nil { return } - nn, err = rwquoted(dst, field) + switch kt { + case IntType: + var i64 int64 + i64, err = src.ReadInt64() + if err != nil { + return + } + src.scratch = strconv.AppendInt(src.scratch[:0], i64, 10) + nn, err = rwquoted(dst, src.scratch) + case UintType: + var u64 uint64 + u64, err = src.ReadUint64() + if err != nil { + return + } + src.scratch = strconv.AppendUint(src.scratch[:0], u64, 10) + nn, err = rwquoted(dst, src.scratch) + default: + field, err = src.ReadMapKeyPtr() + if err != nil { + return + } + nn, err = rwquoted(dst, field) + } n += nn if err != nil { return diff --git a/msgp/json_bytes.go b/msgp/json_bytes.go index 7efd162f..4aced1b1 100644 --- a/msgp/json_bytes.go +++ b/msgp/json_bytes.go @@ -144,6 +144,27 @@ func rwMapBytes(w jsWriter, msg []byte, scratch []byte, depth int) ([]byte, []by } func rwMapKeyBytes(w jsWriter, msg []byte, scratch []byte, depth int) ([]byte, []byte, error) { + if len(msg) < 1 { + return msg, scratch, ErrShortBytes + } + switch getType(msg[0]) { + case IntType: + i, msg, err := ReadInt64Bytes(msg) + if err != nil { + return msg, scratch, err + } + scratch = strconv.AppendInt(scratch[:0], i, 10) + _, err = rwquoted(w, scratch) + return msg, scratch, err + case UintType: + u, msg, err := ReadUint64Bytes(msg) + if err != nil { + return msg, scratch, err + } + scratch = strconv.AppendUint(scratch[:0], u, 10) + _, err = rwquoted(w, scratch) + return msg, scratch, err + } msg, scratch, err := rwStringBytes(w, msg, scratch, depth) if err != nil { if tperr, ok := err.(TypeError); ok && tperr.Encoded == BinType { diff --git a/msgp/json_bytes_test.go b/msgp/json_bytes_test.go index 3b2d58e4..3b604e02 100644 --- a/msgp/json_bytes_test.go +++ b/msgp/json_bytes_test.go @@ -77,6 +77,29 @@ func TestUnmarshalJSON(t *testing.T) { t.Logf("JSON: %s", js.Bytes()) } +func TestUnmarshalAsJSONNumericMapKeys(t *testing.T) { + var buf bytes.Buffer + enc := NewWriter(&buf) + enc.WriteMapHeader(2) + enc.WriteInt64(-5) + enc.WriteString("neg") + enc.WriteUint64(42) + enc.WriteString("pos") + enc.Flush() + + var js bytes.Buffer + if _, err := UnmarshalAsJSON(&js, buf.Bytes()); err != nil { + t.Fatal(err) + } + mp := make(map[string]any) + if err := json.Unmarshal(js.Bytes(), &mp); err != nil { + t.Fatalf("unmarshal: %s — json: %s", err, js.String()) + } + if mp["-5"] != "neg" || mp["42"] != "pos" { + t.Errorf("unexpected map: %v", mp) + } +} + func BenchmarkUnmarshalAsJSON(b *testing.B) { var buf bytes.Buffer enc := NewWriter(&buf) diff --git a/msgp/json_test.go b/msgp/json_test.go index 85455abe..ceaf4f81 100644 --- a/msgp/json_test.go +++ b/msgp/json_test.go @@ -77,6 +77,29 @@ func TestCopyJSON(t *testing.T) { } } +func TestCopyJSONNumericMapKeys(t *testing.T) { + var buf bytes.Buffer + enc := NewWriter(&buf) + enc.WriteMapHeader(2) + enc.WriteInt64(-5) + enc.WriteString("neg") + enc.WriteUint64(42) + enc.WriteString("pos") + enc.Flush() + + var js bytes.Buffer + if _, err := CopyToJSON(&js, &buf); err != nil { + t.Fatal(err) + } + mp := make(map[string]any) + if err := json.Unmarshal(js.Bytes(), &mp); err != nil { + t.Fatalf("unmarshal: %s — json: %s", err, js.String()) + } + if mp["-5"] != "neg" || mp["42"] != "pos" { + t.Errorf("unexpected map: %v", mp) + } +} + // Encoder should generate valid utf-8 even if passed bad input func TestCopyJSONNegativeUTF8(t *testing.T) { // Single string with non-compliant utf-8 byte