Skip to content

Commit ae74eab

Browse files
authored
Merge pull request #3 from nyaruka/json
add json nullable type
2 parents f32bd13 + 92dd275 commit ae74eab

File tree

2 files changed

+137
-0
lines changed

2 files changed

+137
-0
lines changed

null.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,3 +238,64 @@ func (m *Map) UnmarshalJSON(data []byte) error {
238238
}
239239
return json.Unmarshal(data, &m.m)
240240
}
241+
242+
// JSON is a json.RawMessage that will marshall as null when empty or nil.
243+
// null and {} values when unmashalled or scanned from a DB will result in a nil value
244+
type JSON json.RawMessage
245+
246+
// Scan implements the Scanner interface for decoding from a database
247+
func (j *JSON) Scan(src interface{}) error {
248+
if src == nil {
249+
*j = nil
250+
return nil
251+
}
252+
253+
var source []byte
254+
switch src.(type) {
255+
case string:
256+
source = []byte(src.(string))
257+
case []byte:
258+
source = src.([]byte)
259+
default:
260+
return fmt.Errorf("incompatible type for JSON type")
261+
}
262+
263+
if !json.Valid(source) {
264+
return fmt.Errorf("invalid json: %s", source)
265+
}
266+
*j = source
267+
return nil
268+
}
269+
270+
// Value implements the driver Valuer interface
271+
func (j JSON) Value() (driver.Value, error) {
272+
if len(j) == 0 {
273+
return nil, nil
274+
}
275+
return []byte(j), nil
276+
}
277+
278+
// MarshalJSON encodes our JSON to JSON or null
279+
func (j JSON) MarshalJSON() ([]byte, error) {
280+
if len(j) == 0 {
281+
return json.Marshal(nil)
282+
}
283+
return []byte(j), nil
284+
}
285+
286+
// UnmarshalJSON sets our JSON from the passed in JSON
287+
func (j *JSON) UnmarshalJSON(data []byte) error {
288+
if string(data) == "null" {
289+
*j = nil
290+
return nil
291+
}
292+
293+
var jj json.RawMessage
294+
err := json.Unmarshal(data, &jj)
295+
if err != nil {
296+
return err
297+
}
298+
299+
*j = JSON(jj)
300+
return nil
301+
}

null_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"database/sql"
55
"database/sql/driver"
66
"encoding/json"
7+
"strings"
78
"testing"
89

910
_ "github.com/lib/pq"
@@ -368,3 +369,78 @@ func TestMap(t *testing.T) {
368369
assert.Equal(t, m2.GetString(tc.Key, ""), tc.KeyValue)
369370
}
370371
}
372+
373+
func TestJSON(t *testing.T) {
374+
db, err := sql.Open("postgres", "postgres://localhost/null_test?sslmode=disable")
375+
assert.NoError(t, err)
376+
377+
_, err = db.Exec(`DROP TABLE IF EXISTS json_test; CREATE TABLE json_test(value jsonb null);`)
378+
assert.NoError(t, err)
379+
380+
sp := func(s string) *string {
381+
return &s
382+
}
383+
384+
tcs := []struct {
385+
Value JSON
386+
JSON json.RawMessage
387+
DB *string
388+
}{
389+
{JSON(`{"foo":"bar"}`), json.RawMessage(`{"foo":"bar"}`), sp(`{"foo":"bar"}`)},
390+
{JSON(nil), json.RawMessage(`null`), nil},
391+
{JSON([]byte{}), json.RawMessage(`null`), nil},
392+
}
393+
394+
for i, tc := range tcs {
395+
// first test marshalling and unmarshalling to JSON
396+
b, err := json.Marshal(tc.Value)
397+
assert.NoError(t, err)
398+
assert.Equal(t, string(tc.JSON), string(b), "%d: marshalled json not equal", i)
399+
400+
j := JSON("blah")
401+
err = json.Unmarshal(tc.JSON, &j)
402+
assert.NoError(t, err)
403+
assert.Equal(t, string(tc.Value), string(j), "%d: unmarshalled json not equal", i)
404+
405+
// ok, now test writing and reading from DB
406+
_, err = db.Exec(`DELETE FROM json_test;`)
407+
assert.NoError(t, err)
408+
409+
_, err = db.Exec(`INSERT INTO json_test(value) VALUES($1)`, tc.DB)
410+
assert.NoError(t, err)
411+
412+
rows, err := db.Query(`SELECT value FROM json_test;`)
413+
assert.NoError(t, err)
414+
415+
assert.True(t, rows.Next())
416+
j = JSON("blah")
417+
err = rows.Scan(&j)
418+
assert.NoError(t, err)
419+
420+
if tc.Value == nil {
421+
assert.Nil(t, j, "%d: read db value should be null", i)
422+
} else {
423+
assert.Equal(t, string(tc.Value), strings.Replace(string(j), " ", "", -1), "%d: read db value should be equal", i)
424+
}
425+
426+
_, err = db.Exec(`DELETE FROM json_test;`)
427+
assert.NoError(t, err)
428+
429+
_, err = db.Exec(`INSERT INTO json_test(value) VALUES($1)`, tc.Value)
430+
assert.NoError(t, err)
431+
432+
rows, err = db.Query(`SELECT value FROM json_test;`)
433+
assert.NoError(t, err)
434+
435+
assert.True(t, rows.Next())
436+
var s *string
437+
err = rows.Scan(&s)
438+
assert.NoError(t, err)
439+
440+
if tc.DB == nil {
441+
assert.Nil(t, s, "%d: written db value should be null", i)
442+
} else {
443+
assert.Equal(t, *tc.DB, strings.Replace(*s, " ", "", -1), "%d: written db value should be equal", i)
444+
}
445+
}
446+
}

0 commit comments

Comments
 (0)