support marshalling a list of models

This commit is contained in:
Sam Woodard 2015-07-07 09:52:38 -07:00
parent 0525400acb
commit 81bf23f93f
5 changed files with 146 additions and 19 deletions

16
node.go
View File

@ -1,11 +1,21 @@
package jsonapi package jsonapi
type JsonApiPayload struct { type JsonApiOnePayload struct {
Data *JsonApiNode `json:"data"` Data *JsonApiNode `json:"data"`
Included []*JsonApiNode `json:"included,omitempty"` Included []*JsonApiNode `json:"included,omitempty"`
Links *map[string]string `json:"links,omitempty"` Links *map[string]string `json:"links,omitempty"`
} }
type JsonApiManyPayload struct {
Data []*JsonApiNode `json:"data"`
Included []*JsonApiNode `json:"included,omitempty"`
Links *map[string]string `json:"links,omitempty"`
}
type Models interface {
GetData() []interface{}
}
type JsonApiNode struct { type JsonApiNode struct {
Type string `json:"type"` Type string `json:"type"`
Id string `json:"id"` Id string `json:"id"`
@ -13,12 +23,12 @@ type JsonApiNode struct {
Relationships map[string]interface{} `json:"realtionships,omitempty"` Relationships map[string]interface{} `json:"realtionships,omitempty"`
} }
type JsonApiRelationshipSingleNode struct { type JsonApiRelationshipOneNode struct {
Data *JsonApiNode `json:"data"` Data *JsonApiNode `json:"data"`
Links *map[string]string `json:"links,omitempty"` Links *map[string]string `json:"links,omitempty"`
} }
type JsonApiRelationshipMultipleNode struct { type JsonApiRelationshipManyNode struct {
Data []*JsonApiNode `json:"data"` Data []*JsonApiNode `json:"data"`
Links *map[string]string `json:"links,omitempty"` Links *map[string]string `json:"links,omitempty"`
} }

View File

@ -8,7 +8,7 @@ import (
"time" "time"
) )
func UnmarshalJsonApiPayload(payload *JsonApiPayload, model interface{}) error { func UnmarshalJsonApiPayload(payload *JsonApiOnePayload, model interface{}) error {
data := payload.Data data := payload.Data
modelType := reflect.TypeOf(model).Elem() modelType := reflect.TypeOf(model).Elem()

View File

@ -32,8 +32,8 @@ func TestUnmarshalSetsAttrs(t *testing.T) {
} }
} }
func samplePayload() *JsonApiPayload { func samplePayload() *JsonApiOnePayload {
return &JsonApiPayload{ return &JsonApiOnePayload{
Data: &JsonApiNode{ Data: &JsonApiNode{
Id: "5", Id: "5",
Type: "blogs", Type: "blogs",

View File

@ -8,13 +8,45 @@ import (
"time" "time"
) )
func MarshalJsonApiPayload(model interface{}) (*JsonApiPayload, error) { func MarshalJsonApiManyPayload(models Models) (*JsonApiManyPayload, error) {
d := models.GetData()
data := make([]*JsonApiNode, 0, len(d))
incl := make([]*JsonApiNode, 0)
for _, model := range d {
node, included, err := visitModelNode(model)
if err != nil {
return nil, err
}
data = append(data, node)
incl = append(incl, included...)
}
uniqueIncluded := make(map[string]*JsonApiNode)
for i, n := range incl {
k := fmt.Sprintf("%s,%s", n.Type, n.Id)
if uniqueIncluded[k] == nil {
uniqueIncluded[k] = n
} else {
incl = deleteNode(incl, i)
}
}
return &JsonApiManyPayload{
Data: data,
Included: incl,
}, nil
}
func MarshalJsonApiOnePayload(model interface{}) (*JsonApiOnePayload, error) {
rootNode, included, err := visitModelNode(model) rootNode, included, err := visitModelNode(model)
if err != nil { if err != nil {
return nil, err return nil, err
} }
resp := &JsonApiPayload{Data: rootNode} resp := &JsonApiOnePayload{Data: rootNode}
uniqueIncluded := make(map[string]*JsonApiNode) uniqueIncluded := make(map[string]*JsonApiNode)
@ -23,7 +55,7 @@ func MarshalJsonApiPayload(model interface{}) (*JsonApiPayload, error) {
if uniqueIncluded[k] == nil { if uniqueIncluded[k] == nil {
uniqueIncluded[k] = n uniqueIncluded[k] = n
} else { } else {
included = append(included[:i], included[i+1:]...) included = deleteNode(included, i)
} }
} }
@ -111,7 +143,7 @@ func visitModelNode(model interface{}) (*JsonApiNode, []*JsonApiNode, error) {
} }
node.Relationships[args[1]] = &JsonApiRelationshipMultipleNode{Data: shallowNodes} node.Relationships[args[1]] = &JsonApiRelationshipManyNode{Data: shallowNodes}
} else { } else {
er = err er = err
return false return false
@ -124,7 +156,7 @@ func visitModelNode(model interface{}) (*JsonApiNode, []*JsonApiNode, error) {
included = append(included, relationship) included = append(included, relationship)
node.Relationships[args[1]] = &JsonApiRelationshipSingleNode{Data: &shallowNode} node.Relationships[args[1]] = &JsonApiRelationshipOneNode{Data: &shallowNode}
} else { } else {
er = err er = err
return false return false
@ -147,8 +179,8 @@ func visitModelNode(model interface{}) (*JsonApiNode, []*JsonApiNode, error) {
return node, included, nil return node, included, nil
} }
func visitModelNodeRelationships(relationName string, models reflect.Value) (map[string]*JsonApiRelationshipMultipleNode, error) { func visitModelNodeRelationships(relationName string, models reflect.Value) (map[string]*JsonApiRelationshipManyNode, error) {
m := make(map[string]*JsonApiRelationshipMultipleNode) m := make(map[string]*JsonApiRelationshipManyNode)
nodes := make([]*JsonApiNode, 0) nodes := make([]*JsonApiNode, 0)
for i := 0; i < models.Len(); i++ { for i := 0; i < models.Len(); i++ {
@ -160,7 +192,17 @@ func visitModelNodeRelationships(relationName string, models reflect.Value) (map
nodes = append(nodes, node) nodes = append(nodes, node)
} }
m[relationName] = &JsonApiRelationshipMultipleNode{Data: nodes} m[relationName] = &JsonApiRelationshipManyNode{Data: nodes}
return m, nil return m, nil
} }
func deleteNode(a []*JsonApiNode, i int) []*JsonApiNode {
if i < len(a)-1 {
a = append(a[:i], a[i+1:]...)
} else {
a = a[:i]
}
return a
}

View File

@ -24,6 +24,16 @@ type Blog struct {
ViewCount int `jsonapi:"attr,view_count"` ViewCount int `jsonapi:"attr,view_count"`
} }
type Blogs []*Blog
func (b Blogs) GetData() []interface{} {
d := make([]interface{}, len(b))
for i, blog := range b {
d[i] = blog
}
return d
}
func TestHasPrimaryAnnotation(t *testing.T) { func TestHasPrimaryAnnotation(t *testing.T) {
testModel := &Blog{ testModel := &Blog{
Id: 5, Id: 5,
@ -31,7 +41,7 @@ func TestHasPrimaryAnnotation(t *testing.T) {
CreatedAt: time.Now(), CreatedAt: time.Now(),
} }
resp, err := MarshalJsonApiPayload(testModel) resp, err := MarshalJsonApiOnePayload(testModel)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -54,7 +64,7 @@ func TestSupportsAttributes(t *testing.T) {
CreatedAt: time.Now(), CreatedAt: time.Now(),
} }
resp, err := MarshalJsonApiPayload(testModel) resp, err := MarshalJsonApiOnePayload(testModel)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -94,7 +104,7 @@ func TestRelations(t *testing.T) {
}, },
} }
resp, err := MarshalJsonApiPayload(testModel) resp, err := MarshalJsonApiOnePayload(testModel)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -126,7 +136,7 @@ func TestRelations(t *testing.T) {
func TestNoRelations(t *testing.T) { func TestNoRelations(t *testing.T) {
testModel := &Blog{Id: 1, Title: "Title 1", CreatedAt: time.Now()} testModel := &Blog{Id: 1, Title: "Title 1", CreatedAt: time.Now()}
resp, err := MarshalJsonApiPayload(testModel) resp, err := MarshalJsonApiOnePayload(testModel)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -137,7 +147,7 @@ func TestNoRelations(t *testing.T) {
fmt.Printf("%s\n", jsonBuffer.Bytes()) fmt.Printf("%s\n", jsonBuffer.Bytes())
decodedResponse := new(JsonApiPayload) decodedResponse := new(JsonApiOnePayload)
json.NewDecoder(jsonBuffer).Decode(decodedResponse) json.NewDecoder(jsonBuffer).Decode(decodedResponse)
@ -145,3 +155,68 @@ func TestNoRelations(t *testing.T) {
t.Fatalf("Encoding json response did not omit included") t.Fatalf("Encoding json response did not omit included")
} }
} }
func TestMarshalMany(t *testing.T) {
data := Blogs{
&Blog{
Id: 5,
Title: "Title 1",
CreatedAt: time.Now(),
Posts: []*Post{
&Post{
Id: 1,
Title: "Foo",
Body: "Bar",
},
&Post{
Id: 2,
Title: "Fuubar",
Body: "Bas",
},
},
CurrentPost: &Post{
Id: 1,
Title: "Foo",
Body: "Bar",
},
},
&Blog{
Id: 6,
Title: "Title 2",
CreatedAt: time.Now(),
Posts: []*Post{
&Post{
Id: 3,
Title: "Foo",
Body: "Bar",
},
&Post{
Id: 4,
Title: "Fuubar",
Body: "Bas",
},
},
CurrentPost: &Post{
Id: 4,
Title: "Foo",
Body: "Bar",
},
},
}
resp, err := MarshalJsonApiManyPayload(data)
if err != nil {
t.Fatal(err)
}
out := bytes.NewBuffer(nil)
json.NewEncoder(out).Encode(resp)
fmt.Printf("%s\n", out.Bytes())
d := resp.Data
if len(d) != 2 {
t.Fatalf("data should have two elements")
}
}