diff --git a/README.md b/README.md index 146a03c..64c3c5b 100644 --- a/README.md +++ b/README.md @@ -142,23 +142,34 @@ Tag value arguments are comma separated. The first argument must be, `primary`, and the second must be the name that should appear in the `type`\* field for all data objects that represent this type of model. +If the optional argument `readonly` is present the id will not be set when +using `Unmarshal` methods. + \* According the [JSON API](http://jsonapi.org) spec, the plural record types are shown in the examples, but not required. #### `attr` ``` -`jsonapi:"attr,,"` +`jsonapi:"attr,,,"` ``` -These fields' values will end up in the `attributes`hash for a record. -The first argument must be, `attr`, and the second should be the name -for the key to display in the `attributes` hash for that record. The optional -third argument is `omitempty` - if it is present the field will not be present -in the `"attributes"` if the field's value is equivalent to the field types -empty value (ie if the `count` field is of type `int`, `omitempty` will omit the -field when `count` has a value of `0`). Lastly, the spec indicates that -`attributes` key names should be dasherized for multiple word field names. +These fields' values will end up in the `attributes`hash for a record. The +first argument must be, `attr`, and the second should be the name for the key to +display in the `attributes` hash for that record. + +If the optional argument `omitempty` is present the field will not be present in +the `"attributes"` if the field's value is equivalent to the field types empty +value (ie if the `count` field is of type `int`, `omitempty` will omit the field +when `count` has a value of `0`). + +If the optional argument `readonly` is present the field will not be set when +using `Unmarshal` methods. This is useful for performing PATCH operations when +you unmarshal into an existing model to update the fields but don't want to +allow server set fields to be overwritten such as `created_at`. + +Lastly, the spec indicates that `attributes` key names should be dasherized for +multiple word field names. #### `relation` diff --git a/constants.go b/constants.go index d5cd0ea..5d295ae 100644 --- a/constants.go +++ b/constants.go @@ -8,6 +8,7 @@ const ( annotationAttribute = "attr" annotationRelation = "relation" annotationOmitEmpty = "omitempty" + annotationReadOnly = "readonly" annotationISO8601 = "iso8601" annotationSeperator = "," annotationIgnore = "-" diff --git a/request.go b/request.go index 9e0eb1a..9131845 100644 --- a/request.go +++ b/request.go @@ -261,6 +261,14 @@ func handlePrimaryUnmarshal(data *Node, args []string, fieldType reflect.StructF ) } + if len(args) > 2 { + for _, arg := range args[2:] { + if arg == annotationReadOnly { + return nil + } + } + } + // Deal with PTRS var kind reflect.Kind if fieldValue.Kind() == reflect.Ptr { @@ -428,16 +436,21 @@ func handleAttributeUnmarshal(data *Node, args []string, fieldType reflect.Struc return nil } - var iso8601 bool - + var iso8601, readOnly bool if len(args) > 2 { for _, arg := range args[2:] { if arg == annotationISO8601 { iso8601 = true + } else if arg == annotationReadOnly { + readOnly = true } } } + if readOnly { + return nil + } + val := attributes[args[1]] // continue if the attribute was not included in the request diff --git a/request_test.go b/request_test.go index ea9a15f..43222d8 100644 --- a/request_test.go +++ b/request_test.go @@ -794,6 +794,36 @@ func TestEmbededStructs_nilStructPtr(t *testing.T) { } } +func TestUnmarshalReadOnly(t *testing.T) { + type PatchBlog struct { + ID int `jsonapi:"primary,blogs,readonly"` + CreatedAt time.Time `jsonapi:"attr,created_at,omitempty,readonly"` + ViewCount int `jsonapi:"attr,view_count,readonly,omitempty"` + Blog + } + + now := time.Now() + blog := PatchBlog{ + ID: 1, + CreatedAt: now, + ViewCount: 123, + } + + if err := UnmarshalPayload(samplePayloadWithID(), &blog); err != nil { + t.Fatal(err) + } + + if blog.ID != 1 { + t.Errorf("readonly id was set") + } + if blog.CreatedAt != now { + t.Errorf("readonly attr created_at was set") + } + if blog.ViewCount != 123 { + t.Errorf("readonly attr view_count was set") + } +} + func samplePayloadWithoutIncluded() map[string]interface{} { return map[string]interface{}{ "data": map[string]interface{}{ @@ -910,6 +940,7 @@ func samplePayloadWithID() io.Reader { Attributes: map[string]interface{}{ "title": "New blog", "view_count": 1000, + "created_at": time.Now().Add(1 * time.Hour).Unix(), }, }, }