forked from Mirrors/jsonapi
support unmarshal sideloaded records for two-way function
This commit is contained in:
parent
0becfab81d
commit
b112561a72
60
request.go
60
request.go
|
@ -18,10 +18,21 @@ func UnmarshalPayload(in io.Reader, model interface{}) error {
|
|||
return err
|
||||
}
|
||||
|
||||
return unmarshalNode(payload.Data, reflect.ValueOf(model))
|
||||
if payload.Included != nil {
|
||||
includedMap := make(map[string]*Node)
|
||||
for _, included := range payload.Included {
|
||||
key := fmt.Sprintf("%s,%s", included.Type, included.Id)
|
||||
includedMap[key] = included
|
||||
}
|
||||
|
||||
return unmarshalNode(payload.Data, reflect.ValueOf(model), &includedMap)
|
||||
} else {
|
||||
return unmarshalNode(payload.Data, reflect.ValueOf(model), nil)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func unmarshalNode(data *Node, model reflect.Value) error {
|
||||
func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node) error {
|
||||
modelValue := model.Elem()
|
||||
modelType := model.Type().Elem()
|
||||
|
||||
|
@ -128,9 +139,8 @@ func unmarshalNode(data *Node, model reflect.Value) error {
|
|||
|
||||
for _, r := range data {
|
||||
m := reflect.New(fieldValue.Type().Elem().Elem())
|
||||
h := r.(map[string]interface{})
|
||||
|
||||
if err := unmarshalNode(mapToNode(h), m); err != nil {
|
||||
if err := unmarshalMap(r, m, included); err != nil {
|
||||
er = err
|
||||
return false
|
||||
}
|
||||
|
@ -141,9 +151,7 @@ func unmarshalNode(data *Node, model reflect.Value) error {
|
|||
fieldValue.Set(models)
|
||||
} else {
|
||||
m := reflect.New(fieldValue.Type().Elem())
|
||||
h := relationship["data"].(map[string]interface{})
|
||||
|
||||
if err := unmarshalNode(mapToNode(h), m); err != nil {
|
||||
if err := unmarshalMap(relationship["data"], m, included); err != nil {
|
||||
er = err
|
||||
return false
|
||||
}
|
||||
|
@ -166,18 +174,50 @@ func unmarshalNode(data *Node, model reflect.Value) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func mapToNode(m map[string]interface{}) *Node {
|
||||
func unmarshalMap(nodeData interface{}, model reflect.Value, included *map[string]*Node) error {
|
||||
h := nodeData.(map[string]interface{})
|
||||
node := fetchNode(h, included)
|
||||
|
||||
if err := unmarshalNode(node, model, included); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func fetchNode(m map[string]interface{}, included *map[string]*Node) *Node {
|
||||
var attributes map[string]interface{}
|
||||
var relationships map[string]interface{}
|
||||
|
||||
if included != nil {
|
||||
key := fmt.Sprintf("%s,%s", m["type"].(string), m["id"].(string))
|
||||
node := (*included)[key]
|
||||
|
||||
if node != nil {
|
||||
attributes = node.Attributes
|
||||
relationships = node.Relationships
|
||||
}
|
||||
}
|
||||
|
||||
return mapToNode(m, attributes, relationships)
|
||||
}
|
||||
|
||||
func mapToNode(m map[string]interface{}, attributes map[string]interface{}, relationships map[string]interface{}) *Node {
|
||||
node := &Node{Type: m["type"].(string)}
|
||||
|
||||
if m["id"] != nil {
|
||||
node.Id = m["id"].(string)
|
||||
}
|
||||
|
||||
if m["attributes"] != nil {
|
||||
if m["attributes"] == nil {
|
||||
node.Attributes = attributes
|
||||
} else {
|
||||
node.Attributes = m["attributes"].(map[string]interface{})
|
||||
}
|
||||
|
||||
if m["relationships"] != nil {
|
||||
if m["relationships"] == nil {
|
||||
node.Relationships = relationships
|
||||
} else {
|
||||
node.Relationships = m["relationships"].(map[string]interface{})
|
||||
}
|
||||
|
||||
|
|
110
request_test.go
110
request_test.go
|
@ -6,6 +6,7 @@ import (
|
|||
"io"
|
||||
"regexp"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type BadModel struct {
|
||||
|
@ -45,11 +46,6 @@ func TestUnmarshalSetsAttrs(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
//o := bytes.NewBuffer(nil)
|
||||
//json.NewEncoder(o).Encode(out)
|
||||
|
||||
//fmt.Printf("%s\n", o.Bytes())
|
||||
|
||||
if out.CreatedAt.IsZero() {
|
||||
t.Fatalf("Did not parse time")
|
||||
}
|
||||
|
@ -70,7 +66,7 @@ func TestUnmarshalRelationships(t *testing.T) {
|
|||
}
|
||||
|
||||
if out.CurrentPost.Title != "Bas" || out.CurrentPost.Body != "Fuubar" {
|
||||
t.Fatalf("Attributes where not set")
|
||||
t.Fatalf("Attributes were not set")
|
||||
}
|
||||
|
||||
if len(out.Posts) != 2 {
|
||||
|
@ -97,6 +93,48 @@ func TestUnmarshalNestedRelationships(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalRelationshipsSideloaded(t *testing.T) {
|
||||
payload := samplePayloadWithSideloaded()
|
||||
out := new(Blog)
|
||||
|
||||
if err := UnmarshalPayload(payload, out); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if out.CurrentPost == nil {
|
||||
t.Fatalf("Current post was not materialized")
|
||||
}
|
||||
|
||||
if out.CurrentPost.Title != "Foo" || out.CurrentPost.Body != "Bar" {
|
||||
t.Fatalf("Attributes were not set")
|
||||
}
|
||||
|
||||
if len(out.Posts) != 2 {
|
||||
t.Fatalf("There should have been 2 posts")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalNestedRelationshipsSideloaded(t *testing.T) {
|
||||
payload := samplePayloadWithSideloaded()
|
||||
out := new(Blog)
|
||||
|
||||
if err := UnmarshalPayload(payload, out); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if out.CurrentPost == nil {
|
||||
t.Fatalf("Current post was not materialized")
|
||||
}
|
||||
|
||||
if out.CurrentPost.Comments == nil {
|
||||
t.Fatalf("Did not materialize nested records, comments")
|
||||
}
|
||||
|
||||
if len(out.CurrentPost.Comments) != 2 {
|
||||
t.Fatalf("Wrong number of comments")
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalSamplePayload() (*Blog, error) {
|
||||
in := samplePayload()
|
||||
out := new(Blog)
|
||||
|
@ -192,3 +230,63 @@ func samplePayloadWithId() io.Reader {
|
|||
|
||||
return out
|
||||
}
|
||||
|
||||
func samplePayloadWithSideloaded() io.Reader {
|
||||
testModel := &Blog{
|
||||
Id: 5,
|
||||
Title: "Title 1",
|
||||
CreatedAt: time.Now(),
|
||||
Posts: []*Post{
|
||||
&Post{
|
||||
Id: 1,
|
||||
Title: "Foo",
|
||||
Body: "Bar",
|
||||
Comments: []*Comment{
|
||||
&Comment{
|
||||
Id: 1,
|
||||
Body: "foo",
|
||||
},
|
||||
&Comment{
|
||||
Id: 2,
|
||||
Body: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
&Post{
|
||||
Id: 2,
|
||||
Title: "Fuubar",
|
||||
Body: "Bas",
|
||||
Comments: []*Comment{
|
||||
&Comment{
|
||||
Id: 1,
|
||||
Body: "foo",
|
||||
},
|
||||
&Comment{
|
||||
Id: 3,
|
||||
Body: "bas",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
CurrentPost: &Post{
|
||||
Id: 1,
|
||||
Title: "Foo",
|
||||
Body: "Bar",
|
||||
Comments: []*Comment{
|
||||
&Comment{
|
||||
Id: 1,
|
||||
Body: "foo",
|
||||
},
|
||||
&Comment{
|
||||
Id: 2,
|
||||
Body: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out := bytes.NewBuffer(nil)
|
||||
MarshalOnePayload(out, testModel)
|
||||
|
||||
return out
|
||||
}
|
||||
|
|
16
response.go
16
response.go
|
@ -163,10 +163,10 @@ func visitModelNode(model interface{}, sideload bool) (*Node, []*Node, error) {
|
|||
|
||||
if err == nil {
|
||||
if sideload {
|
||||
included = append(included, d...)
|
||||
shallowNodes := make([]*Node, 0)
|
||||
for _, node := range d {
|
||||
included = append(included, node)
|
||||
shallowNodes = append(shallowNodes, cloneAndRemoveAttributes(node))
|
||||
shallowNodes = append(shallowNodes, toShallowNode(node))
|
||||
}
|
||||
|
||||
node.Relationships[args[1]] = &RelationshipManyNode{Data: shallowNodes}
|
||||
|
@ -182,7 +182,7 @@ func visitModelNode(model interface{}, sideload bool) (*Node, []*Node, error) {
|
|||
if err == nil {
|
||||
if sideload {
|
||||
included = append(included, relationship)
|
||||
node.Relationships[args[1]] = &RelationshipOneNode{Data: cloneAndRemoveAttributes(relationship)}
|
||||
node.Relationships[args[1]] = &RelationshipOneNode{Data: toShallowNode(relationship)}
|
||||
} else {
|
||||
node.Relationships[args[1]] = &RelationshipOneNode{Data: relationship}
|
||||
}
|
||||
|
@ -208,11 +208,11 @@ func visitModelNode(model interface{}, sideload bool) (*Node, []*Node, error) {
|
|||
return node, included, nil
|
||||
}
|
||||
|
||||
func cloneAndRemoveAttributes(node *Node) *Node {
|
||||
n := *node
|
||||
n.Attributes = nil
|
||||
|
||||
return &n
|
||||
func toShallowNode(node *Node) *Node {
|
||||
return &Node{
|
||||
Id: node.Id,
|
||||
Type: node.Type,
|
||||
}
|
||||
}
|
||||
|
||||
func visitModelNodeRelationships(relationName string, models reflect.Value, sideload bool) (map[string]*RelationshipManyNode, error) {
|
||||
|
|
Loading…
Reference in New Issue