forked from Mirrors/jsonapi
support marshalling a list of models
This commit is contained in:
parent
0525400acb
commit
81bf23f93f
16
node.go
16
node.go
|
@ -1,11 +1,21 @@
|
|||
package jsonapi
|
||||
|
||||
type JsonApiPayload struct {
|
||||
type JsonApiOnePayload struct {
|
||||
Data *JsonApiNode `json:"data"`
|
||||
Included []*JsonApiNode `json:"included,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 string `json:"type"`
|
||||
Id string `json:"id"`
|
||||
|
@ -13,12 +23,12 @@ type JsonApiNode struct {
|
|||
Relationships map[string]interface{} `json:"realtionships,omitempty"`
|
||||
}
|
||||
|
||||
type JsonApiRelationshipSingleNode struct {
|
||||
type JsonApiRelationshipOneNode struct {
|
||||
Data *JsonApiNode `json:"data"`
|
||||
Links *map[string]string `json:"links,omitempty"`
|
||||
}
|
||||
|
||||
type JsonApiRelationshipMultipleNode struct {
|
||||
type JsonApiRelationshipManyNode struct {
|
||||
Data []*JsonApiNode `json:"data"`
|
||||
Links *map[string]string `json:"links,omitempty"`
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
func UnmarshalJsonApiPayload(payload *JsonApiPayload, model interface{}) error {
|
||||
func UnmarshalJsonApiPayload(payload *JsonApiOnePayload, model interface{}) error {
|
||||
data := payload.Data
|
||||
|
||||
modelType := reflect.TypeOf(model).Elem()
|
||||
|
|
|
@ -32,8 +32,8 @@ func TestUnmarshalSetsAttrs(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func samplePayload() *JsonApiPayload {
|
||||
return &JsonApiPayload{
|
||||
func samplePayload() *JsonApiOnePayload {
|
||||
return &JsonApiOnePayload{
|
||||
Data: &JsonApiNode{
|
||||
Id: "5",
|
||||
Type: "blogs",
|
||||
|
|
58
response.go
58
response.go
|
@ -8,13 +8,45 @@ import (
|
|||
"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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := &JsonApiPayload{Data: rootNode}
|
||||
resp := &JsonApiOnePayload{Data: rootNode}
|
||||
|
||||
uniqueIncluded := make(map[string]*JsonApiNode)
|
||||
|
||||
|
@ -23,7 +55,7 @@ func MarshalJsonApiPayload(model interface{}) (*JsonApiPayload, error) {
|
|||
if uniqueIncluded[k] == nil {
|
||||
uniqueIncluded[k] = n
|
||||
} 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 {
|
||||
er = err
|
||||
return false
|
||||
|
@ -124,7 +156,7 @@ func visitModelNode(model interface{}) (*JsonApiNode, []*JsonApiNode, error) {
|
|||
|
||||
included = append(included, relationship)
|
||||
|
||||
node.Relationships[args[1]] = &JsonApiRelationshipSingleNode{Data: &shallowNode}
|
||||
node.Relationships[args[1]] = &JsonApiRelationshipOneNode{Data: &shallowNode}
|
||||
} else {
|
||||
er = err
|
||||
return false
|
||||
|
@ -147,8 +179,8 @@ func visitModelNode(model interface{}) (*JsonApiNode, []*JsonApiNode, error) {
|
|||
return node, included, nil
|
||||
}
|
||||
|
||||
func visitModelNodeRelationships(relationName string, models reflect.Value) (map[string]*JsonApiRelationshipMultipleNode, error) {
|
||||
m := make(map[string]*JsonApiRelationshipMultipleNode)
|
||||
func visitModelNodeRelationships(relationName string, models reflect.Value) (map[string]*JsonApiRelationshipManyNode, error) {
|
||||
m := make(map[string]*JsonApiRelationshipManyNode)
|
||||
nodes := make([]*JsonApiNode, 0)
|
||||
|
||||
for i := 0; i < models.Len(); i++ {
|
||||
|
@ -160,7 +192,17 @@ func visitModelNodeRelationships(relationName string, models reflect.Value) (map
|
|||
nodes = append(nodes, node)
|
||||
}
|
||||
|
||||
m[relationName] = &JsonApiRelationshipMultipleNode{Data: nodes}
|
||||
m[relationName] = &JsonApiRelationshipManyNode{Data: nodes}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -24,6 +24,16 @@ type Blog struct {
|
|||
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) {
|
||||
testModel := &Blog{
|
||||
Id: 5,
|
||||
|
@ -31,7 +41,7 @@ func TestHasPrimaryAnnotation(t *testing.T) {
|
|||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
resp, err := MarshalJsonApiPayload(testModel)
|
||||
resp, err := MarshalJsonApiOnePayload(testModel)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -54,7 +64,7 @@ func TestSupportsAttributes(t *testing.T) {
|
|||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
resp, err := MarshalJsonApiPayload(testModel)
|
||||
resp, err := MarshalJsonApiOnePayload(testModel)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -94,7 +104,7 @@ func TestRelations(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
resp, err := MarshalJsonApiPayload(testModel)
|
||||
resp, err := MarshalJsonApiOnePayload(testModel)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -126,7 +136,7 @@ func TestRelations(t *testing.T) {
|
|||
func TestNoRelations(t *testing.T) {
|
||||
testModel := &Blog{Id: 1, Title: "Title 1", CreatedAt: time.Now()}
|
||||
|
||||
resp, err := MarshalJsonApiPayload(testModel)
|
||||
resp, err := MarshalJsonApiOnePayload(testModel)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -137,7 +147,7 @@ func TestNoRelations(t *testing.T) {
|
|||
|
||||
fmt.Printf("%s\n", jsonBuffer.Bytes())
|
||||
|
||||
decodedResponse := new(JsonApiPayload)
|
||||
decodedResponse := new(JsonApiOnePayload)
|
||||
|
||||
json.NewDecoder(jsonBuffer).Decode(decodedResponse)
|
||||
|
||||
|
@ -145,3 +155,68 @@ func TestNoRelations(t *testing.T) {
|
|||
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")
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue