Skip to content

Commit

Permalink
Implement unmarshaling
Browse files Browse the repository at this point in the history
  • Loading branch information
mfcochauxlaberge committed Aug 18, 2019
1 parent 7b280f3 commit 0d8b531
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 25 deletions.
152 changes: 132 additions & 20 deletions jsonapi.go
Expand Up @@ -102,10 +102,10 @@ func Marshal(doc *Document, url *URL) ([]byte, error) {
return json.Marshal(plMap)
}

// Unmarshal reads a payload to build and return a document object.
// Unmarshal reads a payload to build and return a Document object.
//
// Both url and schema must not be nil.
func Unmarshal(payload []byte, url *URL, schema *Schema) (*Document, error) {
// schema must not be nil.
func Unmarshal(payload []byte, schema *Schema) (*Document, error) {
doc := &Document{}
ske := &payloadSkeleton{}

Expand All @@ -116,30 +116,23 @@ func Unmarshal(payload []byte, url *URL, schema *Schema) (*Document, error) {
}

// Data
if !url.IsCol && url.RelKind == "" {
typ := schema.GetType(url.ResType)
res := &SoftResource{Type: &typ}
err = json.Unmarshal(ske.Data, res)
if err != nil {
return nil, err
}
doc.Data = res
} else if url.RelKind == "self" {
if !url.IsCol {
inc := Identifier{}
err = json.Unmarshal(ske.Data, &inc)
if len(ske.Data) > 0 {
if ske.Data[0] == '{' {
// Resource
res, err := unmarshalResource(ske.Data, schema)
if err != nil {
return nil, err
}
doc.Data = inc
} else {
incs := Identifiers{}
err = json.Unmarshal(ske.Data, &incs)
doc.Data = res
} else if ske.Data[0] == '[' {
col, err := unmarshalCollection(ske.Data, schema)
if err != nil {
return nil, err
}
doc.Data = incs
doc.Data = col
}
} else {
return nil, NewErrMissingDataMember()
}

// Included
Expand Down Expand Up @@ -171,6 +164,49 @@ func Unmarshal(payload []byte, url *URL, schema *Schema) (*Document, error) {
return doc, nil
}

// UnmarshalIdentifiers reads a payload where the main data is one or more
// identifiers to build and return a Document object.
//
// The included top-level member is ignored.
//
// schema must not be nil.
func UnmarshalIdentifiers(payload []byte, schema *Schema) (*Document, error) {
doc := &Document{}
ske := &payloadSkeleton{}

// Unmarshal
err := json.Unmarshal(payload, ske)
if err != nil {
return nil, err
}

// Identifiers
if len(ske.Data) > 0 {
if ske.Data[0] == '{' {
inc := Identifier{}
err = json.Unmarshal(ske.Data, &inc)
if err != nil {
return nil, err
}
doc.Data = inc
} else if ske.Data[0] == '[' {
incs := Identifiers{}
err = json.Unmarshal(ske.Data, &incs)
if err != nil {
return nil, err
}
doc.Data = incs
}
} else {
return nil, NewErrMissingDataMember()
}

// Meta
doc.Meta = ske.Meta

return doc, nil
}

// marshalResource marshals a Resource into a JSON-encoded payload.
func marshalResource(r Resource, prepath string, fields []string, relData map[string][]string) []byte {
mapPl := map[string]interface{}{}
Expand Down Expand Up @@ -286,3 +322,79 @@ func marshalCollection(c Collection, prepath string, fields []string, relData ma
}
return pl
}

// unmarshalResource unmarshals a JSON-encoded payload into a Resource.
func unmarshalResource(data []byte, schema *Schema) (Resource, error) {
var rske resourceSkeleton
err := json.Unmarshal(data, &rske)
if err != nil {
return nil, err
}

typ := schema.GetType(rske.Type)
res := typ.New()

res.SetID(rske.ID)

for a, v := range rske.Attributes {
if attr, ok := typ.Attrs[a]; ok {
val, err := attr.UnmarshalToType(v)
if err != nil {
return nil, err
}
res.Set(attr.Name, val)
} else {
return nil, NewErrUnknownFieldInBody(typ.Name, a)
}
}
for r, v := range rske.Relationships {
if rel, ok := typ.Rels[r]; ok {
if len(v.Data) > 0 {
if rel.ToOne {
var iden identifierSkeleton
err = json.Unmarshal(v.Data, &iden)
res.SetToOne(rel.Name, iden.ID)
} else {
var idens []identifierSkeleton
err = json.Unmarshal(v.Data, &idens)
ids := make([]string, len(idens))
for i := range idens {
ids[i] = idens[i].ID
}
res.SetToMany(rel.Name, ids)
}
}
if err != nil {
return nil, NewErrInvalidFieldValueInBody(
rel.Name,
string(v.Data),
typ.Name,
)
}
} else {
return nil, NewErrUnknownFieldInBody(typ.Name, r)
}
}

return res, nil
}

// unmarshalCollection unmarshals a JSON-encoded payload into a Collection.
func unmarshalCollection(data []byte, schema *Schema) (Collection, error) {
var cske []json.RawMessage
err := json.Unmarshal(data, &cske)
if err != nil {
return nil, err
}

col := &Resources{}
for i := range cske {
res, err := unmarshalResource(cske[i], schema)
if err != nil {
return nil, err
}
col.Add(res)
}

return col, nil
}
2 changes: 1 addition & 1 deletion request.go
Expand Up @@ -22,7 +22,7 @@ func NewRequest(r *http.Request, schema *Schema) (*Request, error) {

doc := &Document{}
if len(body) > 0 {
doc, err = Unmarshal(body, url, schema)
doc, err = Unmarshal(body, schema)
if err != nil {
return nil, err
}
Expand Down
18 changes: 18 additions & 0 deletions skeletons.go
Expand Up @@ -7,3 +7,21 @@ type payloadSkeleton struct {
Included []json.RawMessage `json:"included"`
Meta map[string]interface{} `json:"meta"`
}

type resourceSkeleton struct {
ID string `json:"id"`
Type string `json:"type"`
Attributes map[string]json.RawMessage `json:"attributes"`
Relationships map[string]relationshipSkeleton `json:"relationships"`
}

type relationshipSkeleton struct {
Data json.RawMessage `json:"data"`
Links map[string]json.RawMessage `json:"links"`
Meta map[string]json.RawMessage `json:"meta"`
}

type identifierSkeleton struct {
ID string `json:"id"`
Type string `json:"type"`
}
15 changes: 11 additions & 4 deletions unmarshaling_test.go
Expand Up @@ -32,12 +32,19 @@ func TestUnmarshalResource(t *testing.T) {
doc1 := NewDocument()
doc1.Data = res1
doc1.Meta = meta1
doc1.RelData["mocktypes3"] = []string{"rel1", "rel2"}

body1, err := Marshal(doc1, url1)
assert.NoError(err)
pl1, err := Unmarshal(body1, url1, schema)

pl1, err := Unmarshal(body1, schema)
assert.NoError(err)
// dst1 := pl1.Data.(Resource)

dst1 := pl1.Data.(Resource)
assert.Equal("mt1", dst1.GetID())
assert.Equal("mt2", dst1.GetToOne("rel1"))
assert.Contains(dst1.GetToMany("rel2"), "mt3")
assert.Contains(dst1.GetToMany("rel2"), "mt4")

// assert.HaveEqualAttributes(t, "same attribues", res1, dst1) TODO Fix test
assert.Equal(meta1, pl1.Meta, "same meta object")
Expand All @@ -64,7 +71,7 @@ func TestUnmarshalIdentifier(t *testing.T) {

body1, err := Marshal(doc1, url1)
assert.NoError(err)
pl1, err := Unmarshal(body1, url1, schema)
pl1, err := UnmarshalIdentifiers(body1, schema)
assert.NoError(err)
dst1 := pl1.Data.(Identifier)

Expand Down Expand Up @@ -99,7 +106,7 @@ func TestUnmarshalIdentifiers(t *testing.T) {
body1, err := Marshal(doc1, url1)
assert.NoError(err)

pl1, err := Unmarshal(body1, url1, schema)
pl1, err := UnmarshalIdentifiers(body1, schema)
assert.NoError(err)

dst1 := pl1.Data.(Identifiers)
Expand Down

0 comments on commit 0d8b531

Please sign in to comment.