Skip to content

Commit 2ea3634

Browse files
committed
Merge pull request #168 from manyminds/bugfix-unmarshaler-interface
Bugfix unmarshaler interface
2 parents 6be652f + 9684184 commit 2ea3634

File tree

2 files changed

+160
-35
lines changed

2 files changed

+160
-35
lines changed

jsonapi/marshal_enum_test.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package jsonapi
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
7+
. "github.com/onsi/ginkgo"
8+
. "github.com/onsi/gomega"
9+
)
10+
11+
type PublishStatus int
12+
13+
const (
14+
StatusUnpublished PublishStatus = iota
15+
StatusPublished
16+
)
17+
18+
var publishStatusValues = []string{
19+
StatusUnpublished: "unpublished",
20+
StatusPublished: "published",
21+
}
22+
23+
func (s PublishStatus) String() string {
24+
if s < 0 || int(s) >= len(publishStatusValues) {
25+
panic("value out of range")
26+
}
27+
return publishStatusValues[s]
28+
}
29+
30+
// MarshalText implements the TextMarshaler interface.
31+
func (s PublishStatus) MarshalText() (text []byte, err error) {
32+
return []byte(s.String()), nil
33+
}
34+
35+
// UnmarshalText implements the TextUnmarshaler interface.
36+
func (s *PublishStatus) UnmarshalText(text []byte) error {
37+
label := string(text)
38+
39+
for key, v := range publishStatusValues {
40+
if v == label {
41+
*s = PublishStatus(key)
42+
return nil
43+
}
44+
}
45+
46+
return fmt.Errorf("invalid value `%s`", label)
47+
}
48+
49+
func (s *PublishStatus) UnmarshalJSON(data []byte) error {
50+
var text string
51+
if err := json.Unmarshal(data, &text); err != nil {
52+
return err
53+
}
54+
return s.UnmarshalText([]byte(text))
55+
}
56+
57+
type EnumPost struct {
58+
ID string `json:"-"`
59+
Title string
60+
Status PublishStatus
61+
}
62+
63+
func (e EnumPost) GetID() string {
64+
return e.ID
65+
}
66+
67+
func (e *EnumPost) SetID(ID string) error {
68+
e.ID = ID
69+
70+
return nil
71+
}
72+
73+
var _ = Describe("Custom enum types", func() {
74+
status := StatusPublished
75+
statusValue := "published"
76+
singleJSON := []byte(`{"data":{"id": "1", "type": "enumPosts", "attributes": {"title":"First Post","status":"published"}}}`)
77+
firstPost := EnumPost{ID: "1", Title: "First Post", Status: StatusPublished}
78+
singlePostMap := map[string]interface{}{
79+
"data": map[string]interface{}{
80+
"id": "1",
81+
"type": "enumPosts",
82+
"attributes": map[string]interface{}{
83+
"title": firstPost.Title,
84+
"status": StatusPublished,
85+
},
86+
},
87+
}
88+
89+
Context("When marshaling objects including enumes", func() {
90+
singlePost := EnumPost{
91+
ID: "1",
92+
Title: "First Post",
93+
Status: StatusPublished,
94+
}
95+
96+
It("marshals JSON", func() {
97+
result, err := MarshalToJSON(singlePost)
98+
Expect(err).ToNot(HaveOccurred())
99+
Expect(result).To(MatchJSON(singleJSON))
100+
})
101+
})
102+
103+
Context("When unmarshaling objects including enums", func() {
104+
105+
It("unmarshals status string values to int enum type", func() {
106+
var result PublishStatus
107+
result.UnmarshalText([]byte(statusValue))
108+
Expect(result).To(Equal(status))
109+
})
110+
111+
It("unmarshals single objects into a struct", func() {
112+
var post EnumPost
113+
err := Unmarshal(singlePostMap, &post)
114+
Expect(err).ToNot(HaveOccurred())
115+
Expect(post).To(Equal(firstPost))
116+
})
117+
118+
It("unmarshals JSON", func() {
119+
var posts []EnumPost
120+
err := UnmarshalFromJSON(singleJSON, &posts)
121+
Expect(err).ToNot(HaveOccurred())
122+
Expect(posts).To(Equal([]EnumPost{firstPost}))
123+
})
124+
})
125+
})

jsonapi/unmarshal.go

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -329,44 +329,44 @@ func setFieldValue(field *reflect.Value, value reflect.Value) (err error) {
329329
}
330330
}()
331331

332-
switch field.Type().Kind() {
333-
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
334-
field.SetInt(int64(value.Float()))
335-
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
336-
field.SetUint(uint64(value.Float()))
337-
case reflect.Slice:
338-
// we always get a []interface{] from json and now need to cast to the right slice type
339-
if value.Type() == reflect.TypeOf([]interface{}{}) {
340-
targetSlice := reflect.MakeSlice(field.Type(), 0, value.Len())
341-
sliceData := value.Interface().([]interface{})
342-
343-
// only iterate over the array if it's not empty
344-
if value.Len() > 0 {
345-
targetType := reflect.TypeOf(sliceData[0])
346-
for _, entry := range sliceData {
347-
casted := reflect.ValueOf(entry).Convert(targetType)
348-
targetSlice = reflect.Append(targetSlice, casted)
349-
}
350-
}
332+
// check for Unmarshaler interface first, after that try to guess the right type
333+
target, ok := field.Addr().Interface().(json.Unmarshaler)
334+
if ok {
335+
marshaledValue, err := json.Marshal(value.Interface())
336+
if err != nil {
337+
return err
338+
}
351339

352-
field.Set(targetSlice)
353-
} else {
354-
// we have the correct type, hm this is only for tests that use direct type at the moment.. we have to refactor the unmarshalling
355-
// anyways..
356-
field.Set(value)
340+
err = target.UnmarshalJSON(marshaledValue)
341+
if err != nil {
342+
return err
357343
}
358-
default:
359-
// try to set it with json.Unmarshaler interface, if that does not work, set value directly
360-
switch target := field.Addr().Interface().(type) {
361-
case json.Unmarshaler:
362-
marshaledValue, err := json.Marshal(value.Interface())
363-
if err != nil {
364-
return err
365-
}
344+
} else {
345+
switch field.Type().Kind() {
346+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
347+
field.SetInt(int64(value.Float()))
348+
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
349+
field.SetUint(uint64(value.Float()))
350+
case reflect.Slice:
351+
// we always get a []interface{] from json and now need to cast to the right slice type
352+
if value.Type() == reflect.TypeOf([]interface{}{}) {
353+
targetSlice := reflect.MakeSlice(field.Type(), 0, value.Len())
354+
sliceData := value.Interface().([]interface{})
355+
356+
// only iterate over the array if it's not empty
357+
if value.Len() > 0 {
358+
targetType := reflect.TypeOf(sliceData[0])
359+
for _, entry := range sliceData {
360+
casted := reflect.ValueOf(entry).Convert(targetType)
361+
targetSlice = reflect.Append(targetSlice, casted)
362+
}
363+
}
366364

367-
err = target.UnmarshalJSON(marshaledValue)
368-
if err != nil {
369-
return err
365+
field.Set(targetSlice)
366+
} else {
367+
// we have the correct type, hm this is only for tests that use direct type at the moment.. we have to refactor the unmarshalling
368+
// anyways..
369+
field.Set(value)
370370
}
371371
default:
372372
field.Set(value)

0 commit comments

Comments
 (0)