Merge branch 'freb-json_marshaling'

This commit is contained in:
Jack Christensen 2020-01-29 09:26:45 -06:00
commit 0ab69ce885
12 changed files with 418 additions and 0 deletions

34
bool.go
View File

@ -2,6 +2,7 @@ package pgtype
import (
"database/sql/driver"
"encoding/json"
"strconv"
errors "golang.org/x/xerrors"
@ -163,3 +164,36 @@ func (src Bool) Value() (driver.Value, error) {
return nil, errUndefined
}
}
func (src Bool) MarshalJSON() ([]byte, error) {
switch src.Status {
case Present:
if src.Bool {
return []byte("true"), nil
} else {
return []byte("false"), nil
}
case Null:
return []byte("null"), nil
case Undefined:
return nil, errUndefined
}
return nil, errBadStatus
}
func (dst *Bool) UnmarshalJSON(b []byte) error {
var v *bool
err := json.Unmarshal(b, &v)
if err != nil {
return err
}
if v == nil {
*dst = Bool{Status: Null}
} else {
*dst = Bool{Bool: *v, Status: Present}
}
return nil
}

View File

@ -95,3 +95,46 @@ func TestBoolAssignTo(t *testing.T) {
}
}
}
func TestBoolMarshalJSON(t *testing.T) {
successfulTests := []struct {
source pgtype.Bool
result string
}{
{source: pgtype.Bool{Status: pgtype.Null}, result: "null"},
{source: pgtype.Bool{Bool: true, Status: pgtype.Present}, result: "true"},
{source: pgtype.Bool{Bool: false, Status: pgtype.Present}, result: "false"},
}
for i, tt := range successfulTests {
r, err := tt.source.MarshalJSON()
if err != nil {
t.Errorf("%d: %v", i, err)
}
if string(r) != tt.result {
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, string(r))
}
}
}
func TestBoolUnmarshalJSON(t *testing.T) {
successfulTests := []struct {
source string
result pgtype.Bool
}{
{source: "null", result: pgtype.Bool{Status: pgtype.Null}},
{source: "true", result: pgtype.Bool{Bool: true, Status: pgtype.Present}},
{source: "false", result: pgtype.Bool{Bool: false, Status: pgtype.Present}},
}
for i, tt := range successfulTests {
var r pgtype.Bool
err := r.UnmarshalJSON([]byte(tt.source))
if err != nil {
t.Errorf("%d: %v", i, err)
}
if r != tt.result {
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
}
}
}

56
date.go
View File

@ -3,6 +3,7 @@ package pgtype
import (
"database/sql/driver"
"encoding/binary"
"encoding/json"
"time"
"github.com/jackc/pgio"
@ -208,3 +209,58 @@ func (src Date) Value() (driver.Value, error) {
return nil, errUndefined
}
}
func (src Date) MarshalJSON() ([]byte, error) {
switch src.Status {
case Null:
return []byte("null"), nil
case Undefined:
return nil, errUndefined
}
if src.Status != Present {
return nil, errBadStatus
}
var s string
switch src.InfinityModifier {
case None:
s = src.Time.Format("2006-01-02")
case Infinity:
s = "infinity"
case NegativeInfinity:
s = "-infinity"
}
return json.Marshal(s)
}
func (dst *Date) UnmarshalJSON(b []byte) error {
var s *string
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
if s == nil {
*dst = Date{Status: Null}
return nil
}
switch *s {
case "infinity":
*dst = Date{Status: Present, InfinityModifier: Infinity}
case "-infinity":
*dst = Date{Status: Present, InfinityModifier: -Infinity}
default:
t, err := time.ParseInLocation("2006-01-02", *s, time.UTC)
if err != nil {
return err
}
*dst = Date{Time: t, Status: Present}
}
return nil
}

View File

@ -116,3 +116,52 @@ func TestDateAssignTo(t *testing.T) {
}
}
}
func TestDateMarshalJSON(t *testing.T) {
successfulTests := []struct {
source pgtype.Date
result string
}{
{source: pgtype.Date{Status: pgtype.Null}, result: "null"},
{source: pgtype.Date{Time: time.Date(2012, 3, 29, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}, result: "\"2012-03-29\""},
{source: pgtype.Date{Time: time.Date(2012, 3, 29, 10, 5, 45, 0, time.FixedZone("", -6*60*60)), Status: pgtype.Present}, result: "\"2012-03-29\""},
{source: pgtype.Date{Time: time.Date(2012, 3, 29, 10, 5, 45, 555*1000*1000, time.FixedZone("", -6*60*60)), Status: pgtype.Present}, result: "\"2012-03-29\""},
{source: pgtype.Date{InfinityModifier: pgtype.Infinity, Status: pgtype.Present}, result: "\"infinity\""},
{source: pgtype.Date{InfinityModifier: pgtype.NegativeInfinity, Status: pgtype.Present}, result: "\"-infinity\""},
}
for i, tt := range successfulTests {
r, err := tt.source.MarshalJSON()
if err != nil {
t.Errorf("%d: %v", i, err)
}
if string(r) != tt.result {
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, string(r))
}
}
}
func TestDateUnmarshalJSON(t *testing.T) {
successfulTests := []struct {
source string
result pgtype.Date
}{
{source: "null", result: pgtype.Date{Status: pgtype.Null}},
{source: "\"2012-03-29\"", result: pgtype.Date{Time: time.Date(2012, 3, 29, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}},
{source: "\"2012-03-29\"", result: pgtype.Date{Time: time.Date(2012, 3, 29, 10, 5, 45, 0, time.FixedZone("", -6*60*60)), Status: pgtype.Present}},
{source: "\"2012-03-29\"", result: pgtype.Date{Time: time.Date(2012, 3, 29, 10, 5, 45, 555*1000*1000, time.FixedZone("", -6*60*60)), Status: pgtype.Present}},
{source: "\"infinity\"", result: pgtype.Date{InfinityModifier: pgtype.Infinity, Status: pgtype.Present}},
{source: "\"-infinity\"", result: pgtype.Date{InfinityModifier: pgtype.NegativeInfinity, Status: pgtype.Present}},
}
for i, tt := range successfulTests {
var r pgtype.Date
err := r.UnmarshalJSON([]byte(tt.source))
if err != nil {
t.Errorf("%d: %v", i, err)
}
if r.Time.Year() != tt.result.Time.Year() || r.Time.Month() != tt.result.Time.Month() || r.Time.Day() != tt.result.Time.Day() || r.Status != tt.result.Status || r.InfinityModifier != tt.result.InfinityModifier {
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
}
}
}

View File

@ -142,6 +142,26 @@ func TestInt4AssignTo(t *testing.T) {
}
}
func TestInt4MarshalJSON(t *testing.T) {
successfulTests := []struct {
source pgtype.Int4
result string
}{
{source: pgtype.Int4{Int: 0, Status: pgtype.Null}, result: "null"},
{source: pgtype.Int4{Int: 1, Status: pgtype.Present}, result: "1"},
}
for i, tt := range successfulTests {
r, err := tt.source.MarshalJSON()
if err != nil {
t.Errorf("%d: %v", i, err)
}
if string(r) != tt.result {
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, string(r))
}
}
}
func TestInt4UnmarshalJSON(t *testing.T) {
successfulTests := []struct {
source string

View File

@ -143,6 +143,26 @@ func TestInt8AssignTo(t *testing.T) {
}
}
func TestInt8MarshalJSON(t *testing.T) {
successfulTests := []struct {
source pgtype.Int8
result string
}{
{source: pgtype.Int8{Int: 0, Status: pgtype.Null}, result: "null"},
{source: pgtype.Int8{Int: 1, Status: pgtype.Present}, result: "1"},
}
for i, tt := range successfulTests {
r, err := tt.source.MarshalJSON()
if err != nil {
t.Errorf("%d: %v", i, err)
}
if string(r) != tt.result {
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, string(r))
}
}
}
func TestInt8UnmarshalJSON(t *testing.T) {
successfulTests := []struct {
source string

23
json.go
View File

@ -165,3 +165,26 @@ func (src JSON) Value() (driver.Value, error) {
return nil, errUndefined
}
}
func (src JSON) MarshalJSON() ([]byte, error) {
switch src.Status {
case Present:
return src.Bytes, nil
case Null:
return []byte("null"), nil
case Undefined:
return nil, errUndefined
}
return nil, errBadStatus
}
func (dst *JSON) UnmarshalJSON(b []byte) error {
if b == nil || string(b) == "null" {
*dst = JSON{Status: Null}
} else {
*dst = JSON{Bytes: b, Status: Present}
}
return nil
}

View File

@ -134,3 +134,44 @@ func TestJSONAssignTo(t *testing.T) {
}
}
}
func TestJSONMarshalJSON(t *testing.T) {
successfulTests := []struct {
source pgtype.JSON
result string
}{
{source: pgtype.JSON{Status: pgtype.Null}, result: "null"},
{source: pgtype.JSON{Bytes: []byte("{\"a\": 1}"), Status: pgtype.Present}, result: "{\"a\": 1}"},
}
for i, tt := range successfulTests {
r, err := tt.source.MarshalJSON()
if err != nil {
t.Errorf("%d: %v", i, err)
}
if string(r) != tt.result {
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, string(r))
}
}
}
func TestJSONUnmarshalJSON(t *testing.T) {
successfulTests := []struct {
source string
result pgtype.JSON
}{
{source: "null", result: pgtype.JSON{Status: pgtype.Null}},
{source: "{\"a\": 1}", result: pgtype.JSON{Bytes: []byte("{\"a\": 1}"), Status: pgtype.Present}},
}
for i, tt := range successfulTests {
var r pgtype.JSON
err := r.UnmarshalJSON([]byte(tt.source))
if err != nil {
t.Errorf("%d: %v", i, err)
}
if string(r.Bytes) != string(tt.result.Bytes) || r.Status != tt.result.Status {
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
}
}
}

View File

@ -68,3 +68,11 @@ func (dst *JSONB) Scan(src interface{}) error {
func (src JSONB) Value() (driver.Value, error) {
return (JSON)(src).Value()
}
func (src JSONB) MarshalJSON() ([]byte, error) {
return (JSON)(src).MarshalJSON()
}
func (dst *JSONB) UnmarshalJSON(b []byte) error {
return (*JSON)(dst).UnmarshalJSON(b)
}

View File

@ -122,6 +122,26 @@ func TestTextAssignTo(t *testing.T) {
}
}
func TestTextMarshalJSON(t *testing.T) {
successfulTests := []struct {
source pgtype.Text
result string
}{
{source: pgtype.Text{String: "", Status: pgtype.Null}, result: "null"},
{source: pgtype.Text{String: "a", Status: pgtype.Present}, result: "\"a\""},
}
for i, tt := range successfulTests {
r, err := tt.source.MarshalJSON()
if err != nil {
t.Errorf("%d: %v", i, err)
}
if string(r) != tt.result {
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, string(r))
}
}
}
func TestTextUnmarshalJSON(t *testing.T) {
successfulTests := []struct {
source string

View File

@ -3,6 +3,7 @@ package pgtype
import (
"database/sql/driver"
"encoding/binary"
"encoding/json"
"time"
"github.com/jackc/pgio"
@ -220,3 +221,59 @@ func (src Timestamptz) Value() (driver.Value, error) {
return nil, errUndefined
}
}
func (src Timestamptz) MarshalJSON() ([]byte, error) {
switch src.Status {
case Null:
return []byte("null"), nil
case Undefined:
return nil, errUndefined
}
if src.Status != Present {
return nil, errBadStatus
}
var s string
switch src.InfinityModifier {
case None:
s = src.Time.Format(time.RFC3339Nano)
case Infinity:
s = "infinity"
case NegativeInfinity:
s = "-infinity"
}
return json.Marshal(s)
}
func (dst *Timestamptz) UnmarshalJSON(b []byte) error {
var s *string
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
if s == nil {
*dst = Timestamptz{Status: Null}
return nil
}
switch *s {
case "infinity":
*dst = Timestamptz{Status: Present, InfinityModifier: Infinity}
case "-infinity":
*dst = Timestamptz{Status: Present, InfinityModifier: -Infinity}
default:
// PostgreSQL uses ISO 8601 for to_json function and casting from a string to timestamptz
tim, err := time.Parse(time.RFC3339Nano, *s)
if err != nil {
return err
}
*dst = Timestamptz{Time: tim, Status: Present}
}
return nil
}

View File

@ -120,3 +120,50 @@ func TestTimestamptzAssignTo(t *testing.T) {
}
}
}
func TestTimestamptzMarshalJSON(t *testing.T) {
successfulTests := []struct {
source pgtype.Timestamptz
result string
}{
{source: pgtype.Timestamptz{Status: pgtype.Null}, result: "null"},
{source: pgtype.Timestamptz{Time: time.Date(2012, 3, 29, 10, 5, 45, 0, time.FixedZone("", -6*60*60)), Status: pgtype.Present}, result: "\"2012-03-29T10:05:45-06:00\""},
{source: pgtype.Timestamptz{Time: time.Date(2012, 3, 29, 10, 5, 45, 555*1000*1000, time.FixedZone("", -6*60*60)), Status: pgtype.Present}, result: "\"2012-03-29T10:05:45.555-06:00\""},
{source: pgtype.Timestamptz{InfinityModifier: pgtype.Infinity, Status: pgtype.Present}, result: "\"infinity\""},
{source: pgtype.Timestamptz{InfinityModifier: pgtype.NegativeInfinity, Status: pgtype.Present}, result: "\"-infinity\""},
}
for i, tt := range successfulTests {
r, err := tt.source.MarshalJSON()
if err != nil {
t.Errorf("%d: %v", i, err)
}
if string(r) != tt.result {
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, string(r))
}
}
}
func TestTimestamptzUnmarshalJSON(t *testing.T) {
successfulTests := []struct {
source string
result pgtype.Timestamptz
}{
{source: "null", result: pgtype.Timestamptz{Status: pgtype.Null}},
{source: "\"2012-03-29T10:05:45-06:00\"", result: pgtype.Timestamptz{Time: time.Date(2012, 3, 29, 10, 5, 45, 0, time.FixedZone("", -6*60*60)), Status: pgtype.Present}},
{source: "\"2012-03-29T10:05:45.555-06:00\"", result: pgtype.Timestamptz{Time: time.Date(2012, 3, 29, 10, 5, 45, 555*1000*1000, time.FixedZone("", -6*60*60)), Status: pgtype.Present}},
{source: "\"infinity\"", result: pgtype.Timestamptz{InfinityModifier: pgtype.Infinity, Status: pgtype.Present}},
{source: "\"-infinity\"", result: pgtype.Timestamptz{InfinityModifier: pgtype.NegativeInfinity, Status: pgtype.Present}},
}
for i, tt := range successfulTests {
var r pgtype.Timestamptz
err := r.UnmarshalJSON([]byte(tt.source))
if err != nil {
t.Errorf("%d: %v", i, err)
}
if !r.Time.Equal(tt.result.Time) || r.Status != tt.result.Status || r.InfinityModifier != tt.result.InfinityModifier {
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
}
}
}