diff --git a/time.go b/time.go new file mode 100644 index 0000000..d02d260 --- /dev/null +++ b/time.go @@ -0,0 +1,31 @@ +package nullable + +import ( + "bytes" + "encoding/json" + "time" +) + +// Time represents a time that may be null or not +// present in json at all. +type Time struct { + Present bool // Present is true if key is present in json + Valid bool // Valid is true if value is not null and valid time + Value time.Time +} + +// UnmarshalJSON implements json.Marshaler interface. +func (t *Time) UnmarshalJSON(data []byte) error { + t.Present = true + + if bytes.Equal(data, null) { + return nil + } + + if err := json.Unmarshal(data, &t.Value); err != nil { + return err + } + + t.Valid = true + return nil +} diff --git a/time_test.go b/time_test.go new file mode 100644 index 0000000..8144df4 --- /dev/null +++ b/time_test.go @@ -0,0 +1,68 @@ +package nullable + +import ( + "bytes" + "encoding/json" + "testing" + "time" +) + +var referenceDate = time.Date(1991, 5, 23, 1, 2, 3, 4, time.UTC) + +func TestTime_UnmarshalJSON(t *testing.T) { + tests := []struct { + name string + buf *bytes.Buffer + expect Time + expectErr error + }{ + { + name: "null value", + buf: bytes.NewBufferString(`{"value":null}`), + expect: Time{ + Present: true, + }, + expectErr: nil, + }, + { + name: "valid value", + buf: bytes.NewBufferString(`{"value":"` + referenceDate.Format(time.RFC3339Nano) + `"}`), + expect: Time{ + Present: true, + Valid: true, + Value: referenceDate, + }, + expectErr: nil, + }, + { + name: "empty", + buf: bytes.NewBufferString(`{}`), + expect: Time{}, + expectErr: nil, + }, + { + name: "unmarshallable", + buf: bytes.NewBufferString(`{"value":42}`), + expect: Time{ + Present: true, + }, + expectErr: &time.ParseError{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + str := struct { + Value Time `json:"value"` + }{} + + if err := json.Unmarshal(tt.buf.Bytes(), &str); !typeMatch(tt.expectErr, err) { + t.Fatalf("unexpected unmarshaling error: %s", err) + } + + got := str.Value + if got.Present != tt.expect.Present || got.Valid != tt.expect.Valid || got.Value != tt.expect.Value { + t.Errorf("expected value to be %#v got %#v", tt.expect, got) + } + }) + } +}