...

Source file src/github.com/jackc/pgx/v5/pgtype/json_test.go

Documentation: github.com/jackc/pgx/v5/pgtype

     1  package pgtype_test
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"database/sql/driver"
     7  	"encoding/json"
     8  	"errors"
     9  	"testing"
    10  
    11  	pgx "github.com/jackc/pgx/v5"
    12  	"github.com/jackc/pgx/v5/pgxtest"
    13  	"github.com/stretchr/testify/require"
    14  )
    15  
    16  func isExpectedEqMap(a any) func(any) bool {
    17  	return func(v any) bool {
    18  		aa := a.(map[string]any)
    19  		bb := v.(map[string]any)
    20  
    21  		if (aa == nil) != (bb == nil) {
    22  			return false
    23  		}
    24  
    25  		if aa == nil {
    26  			return true
    27  		}
    28  
    29  		if len(aa) != len(bb) {
    30  			return false
    31  		}
    32  
    33  		for k := range aa {
    34  			if aa[k] != bb[k] {
    35  				return false
    36  			}
    37  		}
    38  
    39  		return true
    40  	}
    41  }
    42  
    43  func TestJSONCodec(t *testing.T) {
    44  	type jsonStruct struct {
    45  		Name string `json:"name"`
    46  		Age  int    `json:"age"`
    47  	}
    48  
    49  	pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "json", []pgxtest.ValueRoundTripTest{
    50  		{nil, new(*jsonStruct), isExpectedEq((*jsonStruct)(nil))},
    51  		{map[string]any(nil), new(*string), isExpectedEq((*string)(nil))},
    52  		{map[string]any(nil), new([]byte), isExpectedEqBytes([]byte(nil))},
    53  		{[]byte(nil), new([]byte), isExpectedEqBytes([]byte(nil))},
    54  		{nil, new([]byte), isExpectedEqBytes([]byte(nil))},
    55  
    56  		// Test sql.Scanner. (https://github.com/jackc/pgx/issues/1418)
    57  		{"42", new(sql.NullInt64), isExpectedEq(sql.NullInt64{Int64: 42, Valid: true})},
    58  
    59  		// Test driver.Valuer. (https://github.com/jackc/pgx/issues/1430)
    60  		{sql.NullInt64{Int64: 42, Valid: true}, new(sql.NullInt64), isExpectedEq(sql.NullInt64{Int64: 42, Valid: true})},
    61  
    62  		// Test driver.Valuer is used before json.Marshaler (https://github.com/jackc/pgx/issues/1805)
    63  		{Issue1805(7), new(Issue1805), isExpectedEq(Issue1805(7))},
    64  	})
    65  
    66  	pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, pgxtest.KnownOIDQueryExecModes, "json", []pgxtest.ValueRoundTripTest{
    67  		{[]byte("{}"), new([]byte), isExpectedEqBytes([]byte("{}"))},
    68  		{[]byte("null"), new([]byte), isExpectedEqBytes([]byte("null"))},
    69  		{[]byte("42"), new([]byte), isExpectedEqBytes([]byte("42"))},
    70  		{[]byte(`"hello"`), new([]byte), isExpectedEqBytes([]byte(`"hello"`))},
    71  		{[]byte(`"hello"`), new(string), isExpectedEq(`"hello"`)},
    72  		{map[string]any{"foo": "bar"}, new(map[string]any), isExpectedEqMap(map[string]any{"foo": "bar"})},
    73  		{jsonStruct{Name: "Adam", Age: 10}, new(jsonStruct), isExpectedEq(jsonStruct{Name: "Adam", Age: 10})},
    74  	})
    75  }
    76  
    77  type Issue1805 int
    78  
    79  func (i *Issue1805) Scan(src any) error {
    80  	var source []byte
    81  	switch src.(type) {
    82  	case string:
    83  		source = []byte(src.(string))
    84  	case []byte:
    85  		source = src.([]byte)
    86  	default:
    87  		return errors.New("unknown source type")
    88  	}
    89  	var newI int
    90  	if err := json.Unmarshal(source, &newI); err != nil {
    91  		return err
    92  	}
    93  	*i = Issue1805(newI)
    94  	return nil
    95  }
    96  
    97  func (i Issue1805) Value() (driver.Value, error) {
    98  	b, err := json.Marshal(int(i))
    99  	return string(b), err
   100  }
   101  
   102  func (i Issue1805) UnmarshalJSON(bytes []byte) error {
   103  	return errors.New("UnmarshalJSON called")
   104  }
   105  
   106  func (i Issue1805) MarshalJSON() ([]byte, error) {
   107  	return nil, errors.New("MarshalJSON called")
   108  }
   109  
   110  // https://github.com/jackc/pgx/issues/1273#issuecomment-1221414648
   111  func TestJSONCodecUnmarshalSQLNull(t *testing.T) {
   112  	defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
   113  		// Slices are nilified
   114  		slice := []string{"foo", "bar", "baz"}
   115  		err := conn.QueryRow(ctx, "select null::json").Scan(&slice)
   116  		require.NoError(t, err)
   117  		require.Nil(t, slice)
   118  
   119  		// Maps are nilified
   120  		m := map[string]any{"foo": "bar"}
   121  		err = conn.QueryRow(ctx, "select null::json").Scan(&m)
   122  		require.NoError(t, err)
   123  		require.Nil(t, m)
   124  
   125  		m = map[string]interface{}{"foo": "bar"}
   126  		err = conn.QueryRow(ctx, "select null::json").Scan(&m)
   127  		require.NoError(t, err)
   128  		require.Nil(t, m)
   129  
   130  		// Pointer to pointer are nilified
   131  		n := 42
   132  		p := &n
   133  		err = conn.QueryRow(ctx, "select null::json").Scan(&p)
   134  		require.NoError(t, err)
   135  		require.Nil(t, p)
   136  
   137  		// A string cannot scan a NULL.
   138  		str := "foobar"
   139  		err = conn.QueryRow(ctx, "select null::json").Scan(&str)
   140  		require.EqualError(t, err, "can't scan into dest[0]: cannot scan NULL into *string")
   141  
   142  		// A non-string cannot scan a NULL.
   143  		err = conn.QueryRow(ctx, "select null::json").Scan(&n)
   144  		require.EqualError(t, err, "can't scan into dest[0]: cannot scan NULL into *int")
   145  	})
   146  }
   147  
   148  // https://github.com/jackc/pgx/issues/1470
   149  func TestJSONCodecPointerToPointerToString(t *testing.T) {
   150  	defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
   151  		var s *string
   152  		err := conn.QueryRow(ctx, "select '{}'::json").Scan(&s)
   153  		require.NoError(t, err)
   154  		require.NotNil(t, s)
   155  		require.Equal(t, "{}", *s)
   156  
   157  		err = conn.QueryRow(ctx, "select null::json").Scan(&s)
   158  		require.NoError(t, err)
   159  		require.Nil(t, s)
   160  	})
   161  }
   162  
   163  // https://github.com/jackc/pgx/issues/1691
   164  func TestJSONCodecPointerToPointerToInt(t *testing.T) {
   165  	defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
   166  		n := 44
   167  		p := &n
   168  		err := conn.QueryRow(ctx, "select 'null'::jsonb").Scan(&p)
   169  		require.NoError(t, err)
   170  		require.Nil(t, p)
   171  	})
   172  }
   173  
   174  // https://github.com/jackc/pgx/issues/1691
   175  func TestJSONCodecPointerToPointerToStruct(t *testing.T) {
   176  	defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
   177  		type ImageSize struct {
   178  			Height int    `json:"height"`
   179  			Width  int    `json:"width"`
   180  			Str    string `json:"str"`
   181  		}
   182  		is := &ImageSize{Height: 100, Width: 100, Str: "str"}
   183  		err := conn.QueryRow(ctx, `select 'null'::jsonb`).Scan(&is)
   184  		require.NoError(t, err)
   185  		require.Nil(t, is)
   186  	})
   187  }
   188  
   189  func TestJSONCodecClearExistingValueBeforeUnmarshal(t *testing.T) {
   190  	defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
   191  		m := map[string]any{}
   192  		err := conn.QueryRow(ctx, `select '{"foo": "bar"}'::json`).Scan(&m)
   193  		require.NoError(t, err)
   194  		require.Equal(t, map[string]any{"foo": "bar"}, m)
   195  
   196  		err = conn.QueryRow(ctx, `select '{"baz": "quz"}'::json`).Scan(&m)
   197  		require.NoError(t, err)
   198  		require.Equal(t, map[string]any{"baz": "quz"}, m)
   199  	})
   200  }
   201  
   202  type ParentIssue1681 struct {
   203  	Child ChildIssue1681
   204  }
   205  
   206  func (t *ParentIssue1681) MarshalJSON() ([]byte, error) {
   207  	return []byte(`{"custom":"thing"}`), nil
   208  }
   209  
   210  type ChildIssue1681 struct{}
   211  
   212  func (t ChildIssue1681) MarshalJSON() ([]byte, error) {
   213  	return []byte(`{"someVal": false}`), nil
   214  }
   215  
   216  // https://github.com/jackc/pgx/issues/1681
   217  func TestJSONCodecEncodeJSONMarshalerThatCanBeWrapped(t *testing.T) {
   218  	skipCockroachDB(t, "CockroachDB treats json as jsonb. This causes it to format differently than PostgreSQL.")
   219  
   220  	defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
   221  		var jsonStr string
   222  		err := conn.QueryRow(context.Background(), "select $1::json", &ParentIssue1681{}).Scan(&jsonStr)
   223  		require.NoError(t, err)
   224  		require.Equal(t, `{"custom":"thing"}`, jsonStr)
   225  	})
   226  }
   227  

View as plain text