Skip to content

Commit

Permalink
Merge pull request #13 from hashicorp/brandonc/polymorphic_relationships
Browse files Browse the repository at this point in the history
Support polymorphic relationships through a join struct
  • Loading branch information
brandonc authored Nov 15, 2023
2 parents b6a3d21 + bb4d09f commit 360ddf0
Show file tree
Hide file tree
Showing 11 changed files with 750 additions and 76 deletions.
60 changes: 57 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ all of your data easily.

## Example App

[examples/app.go](https://github.com/google/jsonapi/blob/master/examples/app.go)
[examples/app.go](https://github.com/hashicorp/jsonapi/blob/main/examples/app.go)

This program demonstrates the implementation of a create, a show,
and a list [http.Handler](http://golang.org/pkg/net/http#Handler). It
Expand Down Expand Up @@ -179,6 +179,60 @@ used as the key in the `relationships` hash for the record. The optional
third argument is `omitempty` - if present will prevent non existent to-one and
to-many from being serialized.


#### `polyrelation`

```
`jsonapi:"polyrelation,<key name in relationships hash>,<optional: omitempty>"`
```

Polymorphic relations can be represented exactly as relations, except that
an intermediate type is needed within your model struct that provides a choice
for the actual value to be populated within.

Example:

```go
type Video struct {
ID int `jsonapi:"primary,videos"`
SourceURL string `jsonapi:"attr,source-url"`
CaptionsURL string `jsonapi:"attr,captions-url"`
}

type Image struct {
ID int `jsonapi:"primary,images"`
SourceURL string `jsonapi:"attr,src"`
AltText string `jsonapi:"attr,alt"`
}

type OneOfMedia struct {
Video *Video
Image *Image
}

type Post struct {
ID int `jsonapi:"primary,posts"`
Title string `jsonapi:"attr,title"`
Body string `jsonapi:"attr,body"`
Gallery []*OneOfMedia `jsonapi:"polyrelation,gallery"`
Hero *OneOfMedia `jsonapi:"polyrelation,hero"`
}
```

During decoding, the `polyrelation` annotation instructs jsonapi to assign each relationship
to either `Video` or `Image` within the value of the associated field, provided that the
payload contains either a "videos" or "images" type. This field value must be
a pointer to a special choice type struct (also known as a tagged union, or sum type) containing
other pointer fields to jsonapi models. The actual field assignment depends on that type having
a jsonapi "primary" annotation with a type matching the relationship type found in the response.
All other fields will be remain empty. If no matching types are represented by the choice type,
all fields will be empty.

During encoding, the very first non-nil field will be used to populate the payload. Others
will be ignored. Therefore, it's critical to set the value of only one field within the choice
struct. When accepting input values on this type of choice type, it would a good idea to enforce
and check that the value is set on only one field.

#### `links`

*Note: This annotation is an added feature independent of the canonical google/jsonapi package*
Expand Down Expand Up @@ -471,13 +525,13 @@ I use git subtrees to manage dependencies rather than `go get` so that
the src is committed to my repo.

```
git subtree add --squash --prefix=src/github.com/google/jsonapi https://github.com/google/jsonapi.git master
git subtree add --squash --prefix=src/github.com/hashicorp/jsonapi https://github.com/hashicorp/jsonapi.git main
```

To update,

```
git subtree pull --squash --prefix=src/github.com/google/jsonapi https://github.com/google/jsonapi.git master
git subtree pull --squash --prefix=src/github.com/hashicorp/jsonapi https://github.com/hashicorp/jsonapi.git main
```

This assumes that I have my repo structured with a `src` dir containing
Expand Down
21 changes: 11 additions & 10 deletions constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ package jsonapi

const (
// StructTag annotation strings
annotationJSONAPI = "jsonapi"
annotationPrimary = "primary"
annotationClientID = "client-id"
annotationAttribute = "attr"
annotationRelation = "relation"
annotationLinks = "links"
annotationOmitEmpty = "omitempty"
annotationISO8601 = "iso8601"
annotationRFC3339 = "rfc3339"
annotationSeperator = ","
annotationJSONAPI = "jsonapi"
annotationPrimary = "primary"
annotationClientID = "client-id"
annotationAttribute = "attr"
annotationRelation = "relation"
annotationPolyRelation = "polyrelation"
annotationLinks = "links"
annotationOmitEmpty = "omitempty"
annotationISO8601 = "iso8601"
annotationRFC3339 = "rfc3339"
annotationSeparator = ","

iso8601TimeFormat = "2006-01-02T15:04:05Z"

Expand Down
2 changes: 1 addition & 1 deletion examples/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"net/http/httptest"
"time"

"github.com/google/jsonapi"
"github.com/hashicorp/jsonapi"
)

func main() {
Expand Down
2 changes: 1 addition & 1 deletion examples/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"net/http"
"strconv"

"github.com/google/jsonapi"
"github.com/hashicorp/jsonapi"
)

const (
Expand Down
2 changes: 1 addition & 1 deletion examples/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"net/http/httptest"
"testing"

"github.com/google/jsonapi"
"github.com/hashicorp/jsonapi"
)

func TestExampleHandler_post(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion examples/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"fmt"
"time"

"github.com/google/jsonapi"
"github.com/hashicorp/jsonapi"
)

// Blog is a model representing a blog site
Expand Down
24 changes: 24 additions & 0 deletions models_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,27 @@ type CustomAttributeTypes struct {
Float CustomFloatType `jsonapi:"attr,float"`
String CustomStringType `jsonapi:"attr,string"`
}

type Image struct {
ID string `jsonapi:"primary,images"`
Src string `jsonapi:"attr,src"`
}

type Video struct {
ID string `jsonapi:"primary,videos"`
Captions string `jsonapi:"attr,captions"`
}

type OneOfMedia struct {
Image *Image
random int
Video *Video
RandomStuff *string
}

type BlogPostWithPoly struct {
ID string `jsonapi:"primary,blogs"`
Title string `jsonapi:"attr,title"`
Hero *OneOfMedia `jsonapi:"polyrelation,hero-media,omitempty"`
Media []*OneOfMedia `jsonapi:"polyrelation,media,omitempty"`
}
Loading

0 comments on commit 360ddf0

Please sign in to comment.