From 88dfc22ae4aa031783cda90841d5358edd85ff2c Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Sat, 2 Mar 2024 15:12:20 -0600 Subject: [PATCH] Fix simple protocol encoding of json.RawMessage The underlying type of json.RawMessage is a []byte so to avoid it being considered binary data we need to handle it specifically. This is done by registerDefaultPgTypeVariants. In addition, handle json.RawMessage in the JSONCodec PlanEncode to avoid it being mutated by json.Marshal. https://github.com/jackc/pgx/issues/1763 --- pgtype/json.go | 17 +++++++++++++++++ pgtype/pgtype_default.go | 2 ++ pgtype/pgtype_test.go | 8 ++++++++ 3 files changed, 27 insertions(+) diff --git a/pgtype/json.go b/pgtype/json.go index 3f1a750f6..99628092a 100644 --- a/pgtype/json.go +++ b/pgtype/json.go @@ -25,6 +25,11 @@ func (c JSONCodec) PlanEncode(m *Map, oid uint32, format int16, value any) Encod case []byte: return encodePlanJSONCodecEitherFormatByteSlice{} + // Handle json.RawMessage specifically because if it is run through json.Marshal it may be mutated. + // e.g. `{"foo": "bar"}` -> `{"foo":"bar"}`. + case json.RawMessage: + return encodePlanJSONCodecEitherFormatJSONRawMessage{} + // Cannot rely on driver.Valuer being handled later because anything can be marshalled. // // https://github.com/jackc/pgx/issues/1430 @@ -79,6 +84,18 @@ func (encodePlanJSONCodecEitherFormatByteSlice) Encode(value any, buf []byte) (n return buf, nil } +type encodePlanJSONCodecEitherFormatJSONRawMessage struct{} + +func (encodePlanJSONCodecEitherFormatJSONRawMessage) Encode(value any, buf []byte) (newBuf []byte, err error) { + jsonBytes := value.(json.RawMessage) + if jsonBytes == nil { + return nil, nil + } + + buf = append(buf, jsonBytes...) + return buf, nil +} + type encodePlanJSONCodecEitherFormatMarshal struct{} func (encodePlanJSONCodecEitherFormatMarshal) Encode(value any, buf []byte) (newBuf []byte, err error) { diff --git a/pgtype/pgtype_default.go b/pgtype/pgtype_default.go index 58f4b92c7..c21ac081e 100644 --- a/pgtype/pgtype_default.go +++ b/pgtype/pgtype_default.go @@ -1,6 +1,7 @@ package pgtype import ( + "encoding/json" "net" "net/netip" "reflect" @@ -173,6 +174,7 @@ func initDefaultMap() { registerDefaultPgTypeVariants[time.Time](defaultMap, "timestamptz") registerDefaultPgTypeVariants[time.Duration](defaultMap, "interval") registerDefaultPgTypeVariants[string](defaultMap, "text") + registerDefaultPgTypeVariants[json.RawMessage](defaultMap, "json") registerDefaultPgTypeVariants[[]byte](defaultMap, "bytea") registerDefaultPgTypeVariants[net.IP](defaultMap, "inet") diff --git a/pgtype/pgtype_test.go b/pgtype/pgtype_test.go index c397069b1..b670e92b8 100644 --- a/pgtype/pgtype_test.go +++ b/pgtype/pgtype_test.go @@ -546,6 +546,14 @@ func TestMapEncodePlanCacheUUIDTypeConfusion(t *testing.T) { require.Error(t, err) } +// https://github.com/jackc/pgx/issues/1763 +func TestMapEncodeRawJSONIntoUnknownOID(t *testing.T) { + m := pgtype.NewMap() + buf, err := m.Encode(0, pgtype.TextFormatCode, json.RawMessage(`{"foo": "bar"}`), nil) + require.NoError(t, err) + require.Equal(t, []byte(`{"foo": "bar"}`), buf) +} + func BenchmarkMapScanInt4IntoBinaryDecoder(b *testing.B) { m := pgtype.NewMap() src := []byte{0, 0, 0, 42}