Skip to content

Commit b3615c1

Browse files
authored
Merge pull request #251 from jlburkhead/plural
add ability to override default plural check
2 parents 5207f05 + 75f0e60 commit b3615c1

File tree

3 files changed

+149
-8
lines changed

3 files changed

+149
-8
lines changed

jsonapi/fixtures_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,3 +476,26 @@ type ImagePort struct {
476476
Protocol string `json:"protocol"`
477477
Number int `json:"number"`
478478
}
479+
480+
type Article struct {
481+
IDs []string `json:"-"`
482+
Type string `json:"-"`
483+
Name string `json:"-"`
484+
Relationship RelationshipType `json:"-"`
485+
}
486+
487+
func (a Article) GetID() string {
488+
return "id"
489+
}
490+
491+
func (a Article) GetReferences() []Reference {
492+
return []Reference{{Type: a.Type, Name: a.Name, Relationship: a.Relationship}}
493+
}
494+
495+
func (a Article) GetReferencedIDs() []ReferenceID {
496+
referenceIDs := []ReferenceID{}
497+
for _, id := range a.IDs {
498+
referenceIDs = append(referenceIDs, ReferenceID{ID: id, Type: a.Type, Name: a.Name, Relationship: a.Relationship})
499+
}
500+
return referenceIDs
501+
}

jsonapi/marshal.go

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@ import (
88
"strings"
99
)
1010

11+
// RelationshipType is used to specify whether the relationship with referenced objects are to-one or to-many
12+
type RelationshipType int
13+
14+
const (
15+
// DefaultRelationship guesses the relationship type based on the pluralization of the reference name
16+
DefaultRelationship RelationshipType = iota
17+
// ToOneRelationship forces the relationship to be to-one
18+
ToOneRelationship
19+
// ToManyRelationship forces the relationship to be to-many
20+
ToManyRelationship
21+
)
22+
1123
// MarshalIdentifier interface is necessary to give an element
1224
// a unique ID. This interface must be implemented for
1325
// marshal and unmarshal in order to let them store
@@ -19,9 +31,10 @@ type MarshalIdentifier interface {
1931
// ReferenceID contains all necessary information in order
2032
// to reference another struct in jsonapi
2133
type ReferenceID struct {
22-
ID string
23-
Type string
24-
Name string
34+
ID string
35+
Type string
36+
Name string
37+
Relationship RelationshipType
2538
}
2639

2740
// Reference information about possible references of a struct
@@ -30,9 +43,10 @@ type ReferenceID struct {
3043
// Otherwise, if IsNotLoaded is false and GetReferencedIDs() returns no IDs for this reference name, an
3144
// empty `data` field will be added which means that there are no references.
3245
type Reference struct {
33-
Type string
34-
Name string
35-
IsNotLoaded bool
46+
Type string
47+
Name string
48+
IsNotLoaded bool
49+
Relationship RelationshipType
3650
}
3751

3852
// MarshalReferences must be implemented if the struct to be serialized has relations. This must be done
@@ -191,6 +205,13 @@ func marshalData(element MarshalIdentifier, information ServerInformation) (*Dat
191205
return result, err
192206
}
193207

208+
func isToMany(relationshipType RelationshipType, name string) bool {
209+
if relationshipType == DefaultRelationship {
210+
return Pluralize(name) == name
211+
}
212+
return relationshipType == ToManyRelationship
213+
}
214+
194215
// getStructRelationships returns the relationships struct with ids
195216
func getStructRelationships(relationer MarshalLinkedRelations, information ServerInformation) *map[string]Relationship {
196217
referencedIDs := relationer.GetReferencedIDs()
@@ -213,7 +234,7 @@ func getStructRelationships(relationer MarshalLinkedRelations, information Serve
213234
relationships[name] = Relationship{}
214235
// if referenceType is plural, we need to use an array for data, otherwise it's just an object
215236
container := RelationshipDataContainer{}
216-
if Pluralize(name) == name {
237+
if isToMany(referenceIDs[0].Relationship, referenceIDs[0].Name) {
217238
// multiple elements in links
218239
container.DataArray = []RelationshipData{}
219240
for _, referenceID := range referenceIDs {
@@ -247,7 +268,7 @@ func getStructRelationships(relationer MarshalLinkedRelations, information Serve
247268
for name, reference := range notIncludedReferences {
248269
container := RelationshipDataContainer{}
249270
// Plural empty relationships need an empty array and empty to-one need a null in the json
250-
if !reference.IsNotLoaded && Pluralize(name) == name {
271+
if !reference.IsNotLoaded && isToMany(reference.Relationship, reference.Name) {
251272
container.DataArray = []RelationshipData{}
252273
}
253274

jsonapi/marshal_test.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -950,4 +950,101 @@ var _ = Describe("Marshalling", func() {
950950
`))
951951
})
952952
})
953+
954+
Context("when overriding default relationship type", func() {
955+
956+
It("defaults to relationship name pluralization - singular", func() {
957+
article := Article{Name: "author", Relationship: DefaultRelationship}
958+
result, err := Marshal(article)
959+
Expect(err).ToNot(HaveOccurred())
960+
Expect(result).To(MatchJSON(`
961+
{
962+
"data": {
963+
"type": "articles",
964+
"id": "id",
965+
"attributes": {},
966+
"relationships": {
967+
"author": {
968+
"data": null
969+
}
970+
}
971+
}
972+
}
973+
`))
974+
})
975+
976+
It("defaults to relationship name pluralization - plural", func() {
977+
article := Article{Name: "authors", Relationship: DefaultRelationship}
978+
result, err := Marshal(article)
979+
Expect(err).ToNot(HaveOccurred())
980+
Expect(result).To(MatchJSON(`
981+
{
982+
"data": {
983+
"type": "articles",
984+
"id": "id",
985+
"attributes": {},
986+
"relationships": {
987+
"authors": {
988+
"data": []
989+
}
990+
}
991+
}
992+
}
993+
`))
994+
})
995+
996+
It("can make a to-many relationship", func() {
997+
article := Article{
998+
IDs: []string{"1", "2"},
999+
Name: "author",
1000+
Type: "users",
1001+
Relationship: ToManyRelationship,
1002+
}
1003+
result, err := Marshal(article)
1004+
Expect(err).ToNot(HaveOccurred())
1005+
Expect(result).To(MatchJSON(`
1006+
{
1007+
"data": {
1008+
"type": "articles",
1009+
"id": "id",
1010+
"attributes": {},
1011+
"relationships": {
1012+
"author": {
1013+
"data": [
1014+
{"type": "users", "id": "1"},
1015+
{"type": "users", "id": "2"}
1016+
]
1017+
}
1018+
}
1019+
}
1020+
}
1021+
`))
1022+
})
1023+
1024+
It("can make a to-one relationship", func() {
1025+
article := Article{
1026+
IDs: []string{"1"},
1027+
Name: "authors",
1028+
Type: "users",
1029+
Relationship: ToOneRelationship,
1030+
}
1031+
result, err := Marshal(article)
1032+
Expect(err).ToNot(HaveOccurred())
1033+
Expect(result).To(MatchJSON(`
1034+
{
1035+
"data": {
1036+
"type": "articles",
1037+
"id": "id",
1038+
"attributes": {},
1039+
"relationships": {
1040+
"authors": {
1041+
"data": {"type": "users", "id": "1"}
1042+
}
1043+
}
1044+
}
1045+
}
1046+
`))
1047+
})
1048+
1049+
})
9531050
})

0 commit comments

Comments
 (0)