Skip to content

Commit 101503b

Browse files
author
Derek Dowling
committed
Merge pull request #17 from jkongie/jsonapi-relationship-unmarshal
Update Relationship data structures to follow the spec.
2 parents 6b0ce50 + 2cf6c17 commit 101503b

File tree

4 files changed

+114
-7
lines changed

4 files changed

+114
-7
lines changed

object.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ import (
1010

1111
// Object represents the default JSON spec for objects
1212
type Object struct {
13-
Type string `json:"type" valid:"required"`
14-
ID string `json:"id"`
15-
Attributes json.RawMessage `json:"attributes,omitempty"`
16-
Links map[string]*Link `json:"links,omitempty"`
17-
Relationships map[string]*Object `json:"relationships,omitempty"`
13+
Type string `json:"type" valid:"required"`
14+
ID string `json:"id"`
15+
Attributes json.RawMessage `json:"attributes,omitempty"`
16+
Links map[string]*Link `json:"links,omitempty"`
17+
Relationships map[string]*Relationship `json:"relationships,omitempty"`
1818
// Status is the HTTP Status Code that should be associated with the object
1919
// when it is sent.
2020
Status int `json:"-"`
@@ -27,7 +27,7 @@ func NewObject(id string, resourceType string, attributes interface{}) (*Object,
2727
ID: id,
2828
Type: resourceType,
2929
Links: map[string]*Link{},
30-
Relationships: map[string]*Object{},
30+
Relationships: map[string]*Relationship{},
3131
}
3232

3333
rawJSON, err := json.MarshalIndent(attributes, "", " ")

parser_test.go

+20-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,24 @@ func TestParsing(t *testing.T) {
2626

2727
Convey("should parse a valid object", func() {
2828

29-
objectJSON := `{"data": {"type": "user", "id": "sweetID123", "attributes": {"ID":"123"}}}`
29+
objectJSON := `{
30+
"data": {
31+
"type": "user",
32+
"id": "sweetID123",
33+
"attributes": {"ID":"123"},
34+
"relationships": {
35+
"company": {
36+
"data": { "type": "company", "id": "companyID123" }
37+
},
38+
"comments": {
39+
"data": [
40+
{ "type": "comments", "id": "commentID123" },
41+
{ "type": "comments", "id": "commentID456" }
42+
]
43+
}
44+
}
45+
}
46+
}`
3047
req, reqErr := testRequest([]byte(objectJSON))
3148
So(reqErr, ShouldBeNil)
3249

@@ -37,6 +54,8 @@ func TestParsing(t *testing.T) {
3754
So(object.Type, ShouldEqual, "user")
3855
So(object.ID, ShouldEqual, "sweetID123")
3956
So(object.Attributes, ShouldResemble, json.RawMessage(`{"ID":"123"}`))
57+
So(object.Relationships["company"], ShouldResemble, &Relationship{Data: ResourceLinkage{&ResourceIdentifier{Type: "company", ID: "companyID123"}}})
58+
So(object.Relationships["comments"], ShouldResemble, &Relationship{Data: ResourceLinkage{{Type: "comments", ID: "commentID123"}, {Type: "comments", ID: "commentID456"}}})
4059
})
4160

4261
Convey("should reject an object with missing attributes", func() {

relationship.go

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package jsh
2+
3+
import (
4+
"fmt"
5+
6+
"encoding/json"
7+
)
8+
9+
// Relationship represents a reference from the resource object in which it's
10+
// defined to other resource objects.
11+
type Relationship struct {
12+
Links *Links `json:"links,omitempty"`
13+
Data ResourceLinkage `json:"data,omitempty"`
14+
Meta map[string]interface{} `json:"meta,omitempty"`
15+
}
16+
17+
// ResourceLinkage is a typedef around a slice of resource identifiers. This
18+
// allows us to implement a custom UnmarshalJSON.
19+
type ResourceLinkage []*ResourceIdentifier
20+
21+
// ResourceIdentifier identifies an individual resource.
22+
type ResourceIdentifier struct {
23+
Type string `json:"type" valid:"required"`
24+
ID string `json:"id" valid:"required"`
25+
}
26+
27+
/*
28+
UnmarshalJSON allows us to manually decode a the resource linkage via the
29+
json.Unmarshaler interface.
30+
*/
31+
func (rl *ResourceLinkage) UnmarshalJSON(data []byte) error {
32+
// Create a sub-type here so when we call Unmarshal below, we don't recursively
33+
// call this function over and over
34+
type UnmarshalLinkage ResourceLinkage
35+
36+
// if our "List" is a single object, modify the JSON to make it into a list
37+
// by wrapping with "[ ]"
38+
if data[0] == '{' {
39+
data = []byte(fmt.Sprintf("[%s]", data))
40+
}
41+
42+
newLinkage := UnmarshalLinkage{}
43+
44+
err := json.Unmarshal(data, &newLinkage)
45+
if err != nil {
46+
return err
47+
}
48+
49+
convertedLinkage := ResourceLinkage(newLinkage)
50+
*rl = convertedLinkage
51+
52+
return nil
53+
}

relationship_test.go

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package jsh
2+
3+
import (
4+
"testing"
5+
6+
. "github.com/smartystreets/goconvey/convey"
7+
)
8+
9+
func TestRelationship(t *testing.T) {
10+
Convey("ResourceLinkage Tests", t, func() {
11+
Convey("->UnmarshalJSON()", func() {
12+
13+
Convey("should handle a linkage object", func() {
14+
jObj := `{"type": "testRelationship", "id": "ID456"}`
15+
16+
rl := ResourceLinkage{}
17+
err := rl.UnmarshalJSON([]byte(jObj))
18+
So(err, ShouldBeNil)
19+
So(len(rl), ShouldEqual, 1)
20+
})
21+
22+
Convey("should handle a linkage list", func() {
23+
jList := `[
24+
{"type": "testRelationship", "id": "ID456"},
25+
{"type": "testRelationship", "id": "ID789"}
26+
]`
27+
28+
rl := ResourceLinkage{}
29+
err := rl.UnmarshalJSON([]byte(jList))
30+
So(err, ShouldBeNil)
31+
So(len(rl), ShouldEqual, 2)
32+
})
33+
})
34+
})
35+
}

0 commit comments

Comments
 (0)