Skip to content

Commit

Permalink
Merge pull request #17 from jkongie/jsonapi-relationship-unmarshal
Browse files Browse the repository at this point in the history
Update Relationship data structures to follow the spec.
  • Loading branch information
Derek Dowling committed Feb 11, 2016
2 parents 6b0ce50 + 2cf6c17 commit 101503b
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 7 deletions.
12 changes: 6 additions & 6 deletions object.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import (

// Object represents the default JSON spec for objects
type Object struct {
Type string `json:"type" valid:"required"`
ID string `json:"id"`
Attributes json.RawMessage `json:"attributes,omitempty"`
Links map[string]*Link `json:"links,omitempty"`
Relationships map[string]*Object `json:"relationships,omitempty"`
Type string `json:"type" valid:"required"`
ID string `json:"id"`
Attributes json.RawMessage `json:"attributes,omitempty"`
Links map[string]*Link `json:"links,omitempty"`
Relationships map[string]*Relationship `json:"relationships,omitempty"`
// Status is the HTTP Status Code that should be associated with the object
// when it is sent.
Status int `json:"-"`
Expand All @@ -27,7 +27,7 @@ func NewObject(id string, resourceType string, attributes interface{}) (*Object,
ID: id,
Type: resourceType,
Links: map[string]*Link{},
Relationships: map[string]*Object{},
Relationships: map[string]*Relationship{},
}

rawJSON, err := json.MarshalIndent(attributes, "", " ")
Expand Down
21 changes: 20 additions & 1 deletion parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,24 @@ func TestParsing(t *testing.T) {

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

objectJSON := `{"data": {"type": "user", "id": "sweetID123", "attributes": {"ID":"123"}}}`
objectJSON := `{
"data": {
"type": "user",
"id": "sweetID123",
"attributes": {"ID":"123"},
"relationships": {
"company": {
"data": { "type": "company", "id": "companyID123" }
},
"comments": {
"data": [
{ "type": "comments", "id": "commentID123" },
{ "type": "comments", "id": "commentID456" }
]
}
}
}
}`
req, reqErr := testRequest([]byte(objectJSON))
So(reqErr, ShouldBeNil)

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

Convey("should reject an object with missing attributes", func() {
Expand Down
53 changes: 53 additions & 0 deletions relationship.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package jsh

import (
"fmt"

"encoding/json"
)

// Relationship represents a reference from the resource object in which it's
// defined to other resource objects.
type Relationship struct {
Links *Links `json:"links,omitempty"`
Data ResourceLinkage `json:"data,omitempty"`
Meta map[string]interface{} `json:"meta,omitempty"`
}

// ResourceLinkage is a typedef around a slice of resource identifiers. This
// allows us to implement a custom UnmarshalJSON.
type ResourceLinkage []*ResourceIdentifier

// ResourceIdentifier identifies an individual resource.
type ResourceIdentifier struct {
Type string `json:"type" valid:"required"`
ID string `json:"id" valid:"required"`
}

/*
UnmarshalJSON allows us to manually decode a the resource linkage via the
json.Unmarshaler interface.
*/
func (rl *ResourceLinkage) UnmarshalJSON(data []byte) error {
// Create a sub-type here so when we call Unmarshal below, we don't recursively
// call this function over and over
type UnmarshalLinkage ResourceLinkage

// if our "List" is a single object, modify the JSON to make it into a list
// by wrapping with "[ ]"
if data[0] == '{' {
data = []byte(fmt.Sprintf("[%s]", data))
}

newLinkage := UnmarshalLinkage{}

err := json.Unmarshal(data, &newLinkage)
if err != nil {
return err
}

convertedLinkage := ResourceLinkage(newLinkage)
*rl = convertedLinkage

return nil
}
35 changes: 35 additions & 0 deletions relationship_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package jsh

import (
"testing"

. "github.com/smartystreets/goconvey/convey"
)

func TestRelationship(t *testing.T) {
Convey("ResourceLinkage Tests", t, func() {
Convey("->UnmarshalJSON()", func() {

Convey("should handle a linkage object", func() {
jObj := `{"type": "testRelationship", "id": "ID456"}`

rl := ResourceLinkage{}
err := rl.UnmarshalJSON([]byte(jObj))
So(err, ShouldBeNil)
So(len(rl), ShouldEqual, 1)
})

Convey("should handle a linkage list", func() {
jList := `[
{"type": "testRelationship", "id": "ID456"},
{"type": "testRelationship", "id": "ID789"}
]`

rl := ResourceLinkage{}
err := rl.UnmarshalJSON([]byte(jList))
So(err, ShouldBeNil)
So(len(rl), ShouldEqual, 2)
})
})
})
}

0 comments on commit 101503b

Please sign in to comment.