diff --git a/pgtype/json.go b/pgtype/json.go index 753f2410..b7a7101e 100644 --- a/pgtype/json.go +++ b/pgtype/json.go @@ -25,6 +25,13 @@ func (c JSONCodec) PlanEncode(m *Map, oid uint32, format int16, value any) Encod case []byte: return encodePlanJSONCodecEitherFormatByteSlice{} + // Must come before trying wrap encode plans because a pointer to a struct may be unwrapped to a struct that can be + // marshalled. + // + // https://github.com/jackc/pgx/issues/1681 + case json.Marshaler: + return encodePlanJSONCodecEitherFormatMarshal{} + // Cannot rely on driver.Valuer being handled later because anything can be marshalled. // // https://github.com/jackc/pgx/issues/1430 diff --git a/pgtype/json_test.go b/pgtype/json_test.go index f3368a41..a1f24478 100644 --- a/pgtype/json_test.go +++ b/pgtype/json_test.go @@ -133,3 +133,27 @@ func TestJSONCodecClearExistingValueBeforeUnmarshal(t *testing.T) { require.Equal(t, map[string]any{"baz": "quz"}, m) }) } + +type ParentIssue1681 struct { + Child ChildIssue1681 +} + +func (t *ParentIssue1681) MarshalJSON() ([]byte, error) { + return []byte(`{"custom":"thing"}`), nil +} + +type ChildIssue1681 struct{} + +func (t ChildIssue1681) MarshalJSON() ([]byte, error) { + return []byte(`{"someVal": false}`), nil +} + +// https://github.com/jackc/pgx/issues/1681 +func TestJSONCodecEncodeJSONMarshalerThatCanBeWrapped(t *testing.T) { + defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { + var jsonStr string + err := conn.QueryRow(context.Background(), "select $1::json", &ParentIssue1681{}).Scan(&jsonStr) + require.NoError(t, err) + require.Equal(t, `{"custom":"thing"}`, jsonStr) + }) +} diff --git a/pgtype/jsonb_test.go b/pgtype/jsonb_test.go index dfaeb8c4..5bfbdbe3 100644 --- a/pgtype/jsonb_test.go +++ b/pgtype/jsonb_test.go @@ -70,3 +70,13 @@ func TestJSONBCodecUnmarshalSQLNull(t *testing.T) { require.EqualError(t, err, "can't scan into dest[0]: cannot scan NULL into *int") }) } + +// https://github.com/jackc/pgx/issues/1681 +func TestJSONBCodecEncodeJSONMarshalerThatCanBeWrapped(t *testing.T) { + defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { + var jsonStr string + err := conn.QueryRow(context.Background(), "select $1::jsonb", &ParentIssue1681{}).Scan(&jsonStr) + require.NoError(t, err) + require.Equal(t, `{"custom": "thing"}`, jsonStr) // Note that unlike json, jsonb reformats the JSON string. + }) +}