Skip to content
This repository was archived by the owner on Sep 4, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
4965d30
Add ability to unmarshal slice of pointers
omarismail Apr 20, 2021
edf82c9
update go mod
omarismail Apr 20, 2021
7827d7a
Unmarshal Links type to 'links' annotated struct fields
chrisarcand May 11, 2021
4ff67af
Update request.go
chrisarcand May 18, 2021
1e50d74
Merge pull request #10 from hashicorp/add-links-unmarshaling
chrisarcand May 18, 2021
15d5181
Add unmarshaling of interface attribute (#11)
omarismail Aug 17, 2021
12ae986
Allow an extra field to be marshalled for relations
cam-stitt Aug 20, 2021
7401b01
Add allowattrs annotation for relations
cam-stitt Aug 24, 2021
27f7e4a
Add doc on toShallowNode
cam-stitt Aug 24, 2021
4018410
Remove redundant annotation
cam-stitt Aug 25, 2021
6ea81fe
Redundant test logic
cam-stitt Aug 26, 2021
df7b62a
Fix ID logic
cam-stitt Aug 26, 2021
45d18d4
add comment for spec compliance
thrashr888 Aug 26, 2021
ee7dae0
Merge pull request #12 from hashicorp/cs/allow-extra-field-relation
cam-stitt Aug 26, 2021
271d8a0
Add CI workflow for github actions
brandonc Oct 23, 2023
d4bec3a
Adjust go test versions
brandonc Oct 23, 2023
b6a3d21
Merge pull request #14 from hashicorp/TF-10301-setup-ci-for-jsonapi-repo
brandonc Oct 23, 2023
473eb21
Decode polymorphic relationships
brandonc Oct 20, 2023
7460950
Represent fork repo in the README
brandonc Oct 20, 2023
61e814c
Fix spelling of private const
brandonc Oct 20, 2023
9c90a4f
Rename references to "join" or "union" to "choice type"
brandonc Oct 24, 2023
448279a
Don't raise error when an invalid choice node is decoded
brandonc Oct 24, 2023
2a00bb5
Reformat code comments
brandonc Oct 25, 2023
6bdf239
Support public&private non-tagged fields in choice struct
brandonc Oct 25, 2023
43b65c2
Move test models to models_test.go
brandonc Nov 3, 2023
4ebf344
Fixes panic when a has-many relation contains nil
brandonc Nov 3, 2023
6c08cda
Marshal polyrelation
brandonc Nov 3, 2023
e9893b8
Fix go compatibility for <= 1.11
brandonc Nov 4, 2023
1037764
refactor: rename/document chooseFirstNonNilFieldValue
brandonc Nov 6, 2023
960294d
remove unused error return, enhance docs, style fixes
brandonc Nov 6, 2023
bb4d09f
Fix omitted model value for polyrelation fields
brandonc Nov 15, 2023
360ddf0
Merge pull request #13 from hashicorp/brandonc/polymorphic_relationships
brandonc Nov 15, 2023
4d8a31f
Update README.md
brandonc Nov 15, 2023
100f330
Merge pull request #16 from hashicorp/brandonc/degooglifiy
brandonc Nov 15, 2023
0044c38
Adds links to README badges
brandonc Nov 15, 2023
4b7b22a
Merge pull request #17 from hashicorp/brandonc/badge_links
brandonc Nov 15, 2023
0bf163a
feat: introduce nullable types
ctrombley Jan 4, 2024
61c1233
enforce go >=1.18
ctrombley Jan 12, 2024
3414f84
chore: allow overriding of supported nullable type map
ctrombley Jan 12, 2024
93a1527
chore: incorporate review feedback
ctrombley Jan 12, 2024
2acfcfe
tests: update tests
ctrombley Jan 12, 2024
eae9fb6
docs: document NullableAttr and provide example usage
ctrombley Jan 12, 2024
cf85dab
chore: incorporate review feedback
ctrombley Jan 12, 2024
2dbeecf
chore: incorporate review feedback
ctrombley Jan 30, 2024
0ee74e5
Merge pull request #21 from hashicorp/feat/nullable
ctrombley Jan 30, 2024
f45a873
fix: nullable null marshaling
ctrombley Jan 31, 2024
49e11fe
Merge pull request #22 from hashicorp/feat/nullable
ctrombley Jan 31, 2024
a31b22b
add 'source' to error object
jharley Jul 29, 2024
3526b7b
Apply doc. suggestions from code review
jharley Aug 2, 2024
2490a94
Merge pull request #24 from honeycombio/jharley.add-error-source-field
brandonc Aug 2, 2024
d846bbe
Add CODEOWNERS file in .github/CODEOWNERS
mukeshjc Nov 18, 2024
042320d
Merge pull request #26 from mukeshjc/main
mukeshjc Nov 19, 2024
b0c6a5b
fix: handle deprecating relation for polyrelation
notchairmk Dec 23, 2024
9e3a973
chore: comment describing when relation fields are skipped
notchairmk Jan 7, 2025
1dc4f04
Merge pull request #27 from notchairmk/notchairmk/fix-parsing-depreca…
brandonc Jan 7, 2025
9333e5c
Support nested objects within attributes when Marshaling
brandonc Jan 11, 2025
e03a6d4
Add support to Marshal slices of nested objects
brandonc Jan 13, 2025
656e9ed
refactor: visitModelNode
brandonc Jan 14, 2025
abbc3c7
Update CODEOWNERS
brandonc Jan 22, 2025
738c1fd
Merge pull request #28 from hashicorp/marshal_nested_object_attributes
brandonc Jan 22, 2025
0f6f733
Merge pull request #29 from hashicorp/brandonc/core-cloud-co-owners
brandonc Jan 22, 2025
e463f7b
support nested object attributes with json anntns
brandonc Jan 30, 2025
fae13ce
Merge pull request #30 from hashicorp/brandonc/handle_json_annotation…
brandonc Jan 31, 2025
f43aa0d
Avoid recreation of already created included structs, avoids stackove…
Maed223 Feb 3, 2025
863f70e
Add test
Maed223 Feb 3, 2025
0f43dd8
Fix some redundant naming, add explanatory code comment
Maed223 Feb 4, 2025
80e11a9
Merge pull request #32 from hashicorp/TF-22988/circular-inclusions-st…
Maed223 Feb 5, 2025
dfbcbfc
add nullable relationship
netramali Jan 24, 2025
9387e0d
add nil
netramali Feb 3, 2025
9eb4a95
merge conflict
netramali Feb 5, 2025
1ec1b74
Update nullable.go
netramali Feb 5, 2025
8c58fdb
Update nullable.go
netramali Feb 5, 2025
e123c06
Update nullable.go
netramali Feb 5, 2025
876fb53
Merge pull request #31 from hashicorp/netramali/nullable-relationship
netramali Feb 7, 2025
a204b43
revert circular relationship handling
brandonc Feb 19, 2025
74c1838
switch code co-owner order
brandonc Feb 19, 2025
81a76b6
Merge pull request #33 from hashicorp/brandonc/revert_circular_relati…
brandonc Feb 20, 2025
f5d2a4b
new section
netramali Apr 2, 2025
1676b00
new section
netramali Apr 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Each line is a file pattern followed by one or more owners.
# More on CODEOWNERS files: https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners

# Default owners
* @hashicorp/team-ip-compliance
* @hashicorp/tf-core-cloud

# Add override rules below. Each line is a file/folder pattern followed by one or more owners.
# Being an owner means those groups or individuals will be added as reviewers to PRs affecting
# those areas of the code.
# Examples:
# /docs/ @docs-team
# *.js @js-team
# *.go @go-team
22 changes: 22 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: CI
on:
push:
branches:
- main
pull_request:

jobs:
unit-test:
runs-on: ubuntu-latest
strategy:
matrix:
go: [ '1.21', '1.20', '1.19', '1.18']
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

- uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
go-version: ${{ matrix.go }}

- name: test
run: go test -race . -v
13 changes: 0 additions & 13 deletions .travis.yml

This file was deleted.

221 changes: 203 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
# jsonapi

[![Build Status](https://travis-ci.org/google/jsonapi.svg?branch=master)](https://travis-ci.org/google/jsonapi)
[![Go Report Card](https://goreportcard.com/badge/github.com/google/jsonapi)](https://goreportcard.com/report/github.com/google/jsonapi)
[![GoDoc](https://godoc.org/github.com/google/jsonapi?status.svg)](http://godoc.org/github.com/google/jsonapi)
[![No Maintenance Intended](http://unmaintained.tech/badge.svg)](http://unmaintained.tech/)
[![Build Status](https://github.com/hashicorp/jsonapi/actions/workflows/ci.yml/badge.svg?main)](https://github.com/hashicorp/jsonapi/actions/workflows/ci.yml?query=branch%3Amain)
[![Go Report Card](https://goreportcard.com/badge/github.com/hashicorp/jsonapi)](https://goreportcard.com/report/github.com/hashicorp/jsonapi)
[![GoDoc](https://godoc.org/github.com/hashicorp/jsonapi?status.svg)](http://godoc.org/github.com/hashicorp/jsonapi)

A serializer/deserializer for JSON payloads that comply to the
[JSON API - jsonapi.org](http://jsonapi.org) spec in go.

[JSON API - jsonapi.org](http://jsonapi.org) v1.1 spec in go.

This package was forked from [google/jsonapi](https://github.com/google/jsonapi) and
adds several enhancements such as [links](#links) and [polymorphic relationships](#polyrelation).

## Installation

```
go get -u github.com/google/jsonapi
go get -u github.com/hashicorp/jsonapi
```

Or, see [Alternative Installation](#alternative-installation).
Expand Down 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 All @@ -91,9 +91,9 @@ To run,
* Make sure you have [Go installed](https://golang.org/doc/install)
* Create the following directories or similar: `~/go`
* Set `GOPATH` to `PWD` in your shell session, `export GOPATH=$PWD`
* `go get github.com/google/jsonapi`. (Append `-u` after `get` if you
* `go get github.com/hashicorp/jsonapi`. (Append `-u` after `get` if you
are updating.)
* `cd $GOPATH/src/github.com/google/jsonapi/examples`
* `cd $GOPATH/src/github.com/hashicorp/jsonapi/examples`
* `go build && ./examples`

## `jsonapi` Tag Reference
Expand Down Expand Up @@ -179,6 +179,69 @@ 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`
```
`jsonapi:"links,omitempty"`
```

A field annotated with `links` will have the links members of the request unmarshaled to it. Note
that this field should _always_ be annotated with `omitempty`, as marshaling of links members is
instead handled by the `Linkable` interface (see `Links` below).

## Methods Reference

**All `Marshal` and `Unmarshal` methods expect pointers to struct
Expand All @@ -190,7 +253,7 @@ about the rest?
### Create Record Example

You can Unmarshal a JSON API payload using
[jsonapi.UnmarshalPayload](http://godoc.org/github.com/google/jsonapi#UnmarshalPayload).
[jsonapi.UnmarshalPayload](http://godoc.org/github.com/hashicorp/jsonapi#UnmarshalPayload).
It reads from an [io.Reader](https://golang.org/pkg/io/#Reader)
containing a JSON API payload for one record (but can have related
records). Then, it materializes a struct that you created and passed in
Expand All @@ -199,7 +262,7 @@ the top level, in request payloads at the moment. Bulk creates and
updates are not supported yet.

After saving your record, you can use,
[MarshalOnePayload](http://godoc.org/github.com/google/jsonapi#MarshalOnePayload),
[MarshalOnePayload](http://godoc.org/github.com/hashicorp/jsonapi#MarshalOnePayload),
to write the JSON API response to an
[io.Writer](https://golang.org/pkg/io/#Writer).

Expand All @@ -209,15 +272,15 @@ to write the JSON API response to an
UnmarshalPayload(in io.Reader, model interface{})
```

Visit [godoc](http://godoc.org/github.com/google/jsonapi#UnmarshalPayload)
Visit [godoc](http://godoc.org/github.com/hashicorp/jsonapi#UnmarshalPayload)

#### `MarshalPayload`

```go
MarshalPayload(w io.Writer, models interface{}) error
```

Visit [godoc](http://godoc.org/github.com/google/jsonapi#MarshalPayload)
Visit [godoc](http://godoc.org/github.com/hashicorp/jsonapi#MarshalPayload)

Writes a JSON API response, with related records sideloaded, into an
`included` array. This method encodes a response for either a single record or
Expand Down Expand Up @@ -253,7 +316,7 @@ func CreateBlog(w http.ResponseWriter, r *http.Request) {
UnmarshalManyPayload(in io.Reader, t reflect.Type) ([]interface{}, error)
```

Visit [godoc](http://godoc.org/github.com/google/jsonapi#UnmarshalManyPayload)
Visit [godoc](http://godoc.org/github.com/hashicorp/jsonapi#UnmarshalManyPayload)

Takes an `io.Reader` and a `reflect.Type` representing the uniform type
contained within the `"data"` JSON API member.
Expand Down Expand Up @@ -346,6 +409,128 @@ func (post Post) JSONAPIRelationshipMeta(relation string) *Meta {
}
```

### Nullable attributes

Certain APIs may interpret the meaning of `null` attribute values as significantly
different from unspecified values (those that do not show up in the request).
The default use of the `omitempty` struct tag does not allow for sending
significant `null`s.

A type is provided for this purpose if needed: `NullableAttr[T]`. This type
provides an API for sending and receiving significant `null` values for
attribute values of any type.

In the example below, a payload is presented for a fictitious API that makes use
of significant `null` values. Once enabled, the `UnsettableTime` setting can
only be disabled by updating it to a `null` value.

The payload struct below makes use of a `NullableAttr` with an inner `time.Time`
to allow this behavior:

```go
type Settings struct {
ID int `jsonapi:"primary,videos"`
UnsettableTime jsonapi.NullableAttr[time.Time] `jsonapi:"attr,unsettable_time,rfc3339,omitempty"`
}
```

To enable the setting as described above, an non-null `time.Time` value is
sent to the API. This is done by using the exported
`NewNullableAttrWithValue[T]()` method:

```go
s := Settings{
ID: 1,
UnsettableTime: jsonapi.NewNullableAttrWithValue[time.Time](time.Now()),
}
```

To disable the setting, a `null` value needs to be sent to the API. This is done
by using the exported `NewNullNullableAttr[T]()` method:

```go
s := Settings{
ID: 1,
UnsettableTime: jsonapi.NewNullNullableAttr[time.Time](),
}
```

Once a payload has been marshaled, the attribute value is flattened to a
primitive value:
```
"unsettable_time": "2021-01-01T02:07:14Z",
```

Significant nulls are also included and flattened, even when specifying `omitempty`:
```
"unsettable_time": null,
```

Once a payload is unmarshaled, the target attribute field is hydrated with
the value in the payload and can be retrieved with the `Get()` method:
```go
t, err := s.UnsettableTime.Get()
```

All other struct tags used in the attribute definition will be honored when
marshaling and unmarshaling non-null values for the inner type.

### Nullable Relationship

The `NullableRelationship` type is a generic type used to handle relationships in JSON API payloads that is not set, set to null or set to a valid relationship in the request. This type provides an API for sending and receiving significant `null` values for relationship values of any type.

In the example below, a payload is presented for a fictitious API that makes use of significant `null` values. Once enabled, the `NullableComment` relationship can only be disabled by updating it to a `null` value.

The payload struct below makes use of a `NullableRelationship` with an inner `*Comment` to allow this behavior:

```go
type WithNullableAttrs struct {
RFC3339Time jsonapi.NullableAttr[time.Time] `jsonapi:"attr,rfc3339_time,rfc3339,omitempty"`
ISO8601Time jsonapi.NullableAttr[time.Time] `jsonapi:"attr,iso8601_time,iso8601,omitempty"`
Bool jsonapi.NullableAttr[bool] `jsonapi:"attr,bool,omitempty"`
NullableComment jsonapi.NullableRelationship[*Comment] `jsonapi:"relation,nullable_comment,omitempty"`
}
```

To enable the relationship as described above, a non-null `*Comment` value is sent to the API. This is done by using the exported `NewNullableRelationshipWithValue[T]()` method:

```go
comment := &Comment{
ID: 5,
Body: "Hello World",
}

s := WithNullableAttrs{
NullableComment: jsonapi.NewNullableRelationshipWithValue[*Comment](comment),
}
```

To disable the relationship, a `null` value needs to be sent to the API. This is done by using the exported `NewNullNullableRelationship[T]()` method:

```go
s := WithNullableAttrs{
NullableComment: jsonapi.NewNullNullableRelationship[*Comment](),
}
```

Once a payload has been marshaled, the relationship value is flattened to a reference value:

```json
"nullable_comment": {"data": {"type": "comments", "id": "5"}}
```

Significant nulls are also included and flattened, even when specifying `omitempty`:

```json
"nullable_comment": {"data": null}
```

Once a payload is unmarshaled, the target relationship field is hydrated with the value in the payload and can be retrieved with the `Get()` method:

```go
nullableComment, err := s.NullableComment.Get()
```

### Custom types

Custom types are supported for primitive types, only, as attributes. Examples,
Expand Down Expand Up @@ -419,7 +604,7 @@ if err := validate(&myStructToValidate); err != nil {
MarshalOnePayloadEmbedded(w io.Writer, model interface{}) error
```

Visit [godoc](http://godoc.org/github.com/google/jsonapi#MarshalOnePayloadEmbedded)
Visit [godoc](http://godoc.org/github.com/hashicorp/jsonapi#MarshalOnePayloadEmbedded)

This method is not strictly meant to for use in implementation code,
although feel free. It was mainly created for use in tests; in most cases,
Expand Down Expand Up @@ -459,13 +644,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
Loading