Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom types #104

Open
wants to merge 29 commits into
base: feature/embeded-structs
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
53197ad
working version
skimata Feb 4, 2017
9045ea9
fix text
skimata Feb 6, 2017
7bb11b8
combine test files
skimata Feb 6, 2017
87fcc79
move private funcs to bottom
skimata Feb 6, 2017
1b5f1b4
ErrInvalidType should ignore interfaces
skimata Feb 21, 2017
06cdde6
replace MarshalOnePayload w/ MarshalPayload; fix bug w/ node merge()
skimata Jul 18, 2017
01c2432
minor tweaks; address a couple comments
skimata Jul 18, 2017
ad5f5cd
decompose unmarshalNode() to smaller funcs; unmarshal should go from …
skimata Jul 18, 2017
ab94c5a
deep copy the node when passing relation/sideloaded notes to unmarshal()
skimata Jul 19, 2017
7d26540
add some comments and do some additional cleanup
skimata Jul 19, 2017
f79a192
add test uses annotationIgnore
skimata Jul 19, 2017
deeffb7
implement support for struct fields that implement json.Marshaler/Unm…
skimata Jul 20, 2017
c66d1da
add additional test that compares marshal/unmarshal behavior w/ stand…
skimata Jul 20, 2017
218abd9
add support for pointer embedded structs
skimata Jul 21, 2017
7f51be8
add support for slice of json.Marshaler/Unmarshaler; add UnixMilli type
skimata Jul 25, 2017
fc52cdf
add support for maps of json.Marshaler/Unmarshaler
skimata Jul 25, 2017
405fe10
additional tests for marshal/unmarshal behavior of custom types
skimata Jul 25, 2017
3aaeac8
add float/exponential notation support for UnixMilli
skimata Jul 26, 2017
ddc78e5
fix TestEmbededStructs_nonNilStructPtr; bug on loop w/ (multiple) emb…
skimata Jul 28, 2017
01f918e
fix TestMarshal_duplicatePrimaryAnnotationFromEmbeddedStructs; fix or…
skimata Jul 28, 2017
110f01b
Merge branch 'feature/embeded-structs-fix-tests' into custom-types
skimata Jul 28, 2017
d874c21
make ISO8601Datetime and UnixMilli private
skimata Jul 28, 2017
75748b1
clean up comment
skimata Jul 28, 2017
1a042c5
simplify and refactor handleAttributeUnmarshal()
skimata Jul 28, 2017
db8ca4e
cleanup comment
skimata Jul 28, 2017
bf72c7a
improve check on json.Unmarshaler implementations
skimata Jul 31, 2017
e55ed48
cleanup handlePrimaryUnmarshal()
skimata Jul 31, 2017
37d04ea
rename fieldType to structField
skimata Jul 31, 2017
a6437e5
remove 'ErrInvalidType' response; allow all json lib supported types …
skimata Aug 16, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions attributes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package jsonapi

import (
"encoding/json"
"reflect"
"strconv"
"strings"
"time"
)

// NOTE: reciever for MarshalJSON() should not be a pointer
// https://play.golang.org/p/Cf9yYLIzJA (MarshalJSON() w/ pointer reciever)
// https://play.golang.org/p/5EsItAtgXy (MarshalJSON() w/o pointer reciever)

const iso8601Layout = "2006-01-02T15:04:05Z07:00"

var (
jsonUnmarshaler = reflect.TypeOf(new(json.Unmarshaler)).Elem()
)

// iso8601Datetime represents a ISO8601 formatted datetime
// It is a time.Time instance that marshals and unmarshals to the ISO8601 ref
type iso8601Datetime struct {
time.Time
}

// MarshalJSON implements the json.Marshaler interface.
func (t iso8601Datetime) MarshalJSON() ([]byte, error) {
s := t.Time.Format(iso8601Layout)
return json.Marshal(s)
}

// UnmarshalJSON implements the json.Unmarshaler interface.
func (t *iso8601Datetime) UnmarshalJSON(data []byte) error {
// Ignore null, like in the main JSON package.
if string(data) == "null" {
return nil
}
// Fractional seconds are handled implicitly by Parse.
var err error
if t.Time, err = time.Parse(strconv.Quote(iso8601Layout), string(data)); err != nil {
return ErrInvalidISO8601
}
return err
}

// iso8601Datetime.String() - override default String() on time
func (t iso8601Datetime) String() string {
return t.Format(iso8601Layout)
}

// unix(Unix Seconds) marshals/unmarshals the number of milliseconds elapsed since January 1, 1970 UTC
type unix struct {
time.Time
}

// MarshalJSON implements the json.Marshaler interface.
func (t unix) MarshalJSON() ([]byte, error) {
return json.Marshal(t.Unix())
}

// UnmarshalJSON implements the json.Unmarshaler interface.
func (t *unix) UnmarshalJSON(data []byte) error {
// Ignore null, like in the main JSON package.
s := string(data)
if s == "null" {
return nil
}

v, err := stringToInt64(s)
if err != nil {
// return this specific error to maintain existing tests.
// TODO: consider refactoring tests to not assert against error string
return ErrInvalidTime
}

t.Time = time.Unix(v, 0).In(time.UTC)

return nil
}

// unixMilli (Unix Millisecond) marshals/unmarshals the number of milliseconds elapsed since January 1, 1970 UTC
type unixMilli struct {
time.Time
}

// MarshalJSON implements the json.Marshaler interface.
func (t unixMilli) MarshalJSON() ([]byte, error) {
return json.Marshal(t.UnixNano() / int64(time.Millisecond))
}

// UnmarshalJSON implements the json.Unmarshaler interface.
func (t *unixMilli) UnmarshalJSON(data []byte) error {
// Ignore null, like in the main JSON package.
s := string(data)
if s == "null" {
return nil
}

v, err := stringToInt64(s)
if err != nil {
return err
}

t.Time = time.Unix(v/1000, (v % 1000 * int64(time.Millisecond))).In(time.UTC)

return nil
}

// stringToInt64 convert time in either decimal or exponential notation to int64
// https://golang.org/doc/go1.8#encoding_json
// go1.8 prefers decimal notation
// go1.7 may use exponetial notation, so check if it came in as a float
func stringToInt64(s string) (int64, error) {
var v int64
if strings.Contains(s, ".") {
fv, err := strconv.ParseFloat(s, 64)
if err != nil {
return v, err
}
v = int64(fv)
} else {
iv, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return v, err
}
v = iv
}
return v, nil
}

func implementsJSONUnmarshaler(t reflect.Type) bool {
ok, _ := deepCheckImplementation(t, jsonUnmarshaler)
return ok
}

func deepCheckImplementation(t, interfaceType reflect.Type) (bool, reflect.Type) {
// check as-is
if t.Implements(interfaceType) {
return true, t
}

switch t.Kind() {
case reflect.Array, reflect.Chan, reflect.Map, reflect.Ptr, reflect.Slice:
// check ptr implementation
ptrType := reflect.PtrTo(t)
if ptrType.Implements(interfaceType) {
return true, ptrType
}
// since these are reference types, re-check on the element of t
return deepCheckImplementation(t.Elem(), interfaceType)
default:
// check ptr implementation
ptrType := reflect.PtrTo(t)
if ptrType.Implements(interfaceType) {
return true, ptrType
}
// nothing else to check, return false
return false, nil
}
}
Loading