jsonapi/embeded_structs_test.go

1001 lines
24 KiB
Go

package jsonapi
import (
"bytes"
"encoding/json"
"reflect"
"testing"
)
func TestMergeNode(t *testing.T) {
parent := &Node{
Type: "Good",
ID: "99",
Attributes: map[string]interface{}{"fizz": "buzz"},
}
child := &Node{
Type: "Better",
ClientID: "1111",
Attributes: map[string]interface{}{"timbuk": 2},
}
expected := &Node{
Type: "Better",
ID: "99",
ClientID: "1111",
Attributes: map[string]interface{}{"fizz": "buzz", "timbuk": 2},
}
parent.merge(child)
if !reflect.DeepEqual(expected, parent) {
t.Errorf("Got %+v Expected %+v", parent, expected)
}
}
func TestIsEmbeddedStruct(t *testing.T) {
type foo struct{}
structType := reflect.TypeOf(foo{})
stringType := reflect.TypeOf("")
if structType.Kind() != reflect.Struct {
t.Fatal("structType.Kind() is not a struct.")
}
if stringType.Kind() != reflect.String {
t.Fatal("stringType.Kind() is not a string.")
}
type test struct {
scenario string
input reflect.StructField
expectedRes bool
}
tests := []test{
test{
scenario: "success",
input: reflect.StructField{Anonymous: true, Type: structType},
expectedRes: true,
},
test{
scenario: "wrong type",
input: reflect.StructField{Anonymous: true, Type: stringType},
expectedRes: false,
},
test{
scenario: "not embedded",
input: reflect.StructField{Type: structType},
expectedRes: false,
},
}
for _, test := range tests {
res := isEmbeddedStruct(test.input)
if res != test.expectedRes {
t.Errorf(
"Scenario -> %s\nGot -> %v\nExpected -> %v\n",
test.scenario, res, test.expectedRes)
}
}
}
func TestShouldIgnoreField(t *testing.T) {
type test struct {
scenario string
input string
expectedRes bool
}
tests := []test{
test{
scenario: "opt-out",
input: annotationIgnore,
expectedRes: true,
},
test{
scenario: "no tag",
input: "",
expectedRes: false,
},
test{
scenario: "wrong tag",
input: "wrong,tag",
expectedRes: false,
},
}
for _, test := range tests {
res := shouldIgnoreField(test.input)
if res != test.expectedRes {
t.Errorf(
"Scenario -> %s\nGot -> %v\nExpected -> %v\n",
test.scenario, res, test.expectedRes)
}
}
}
func TestIsValidEmbeddedStruct(t *testing.T) {
type foo struct{}
structType := reflect.TypeOf(foo{})
stringType := reflect.TypeOf("")
if structType.Kind() != reflect.Struct {
t.Fatal("structType.Kind() is not a struct.")
}
if stringType.Kind() != reflect.String {
t.Fatal("stringType.Kind() is not a string.")
}
type test struct {
scenario string
input reflect.StructField
expectedRes bool
}
tests := []test{
test{
scenario: "success",
input: reflect.StructField{Anonymous: true, Type: structType},
expectedRes: true,
},
test{
scenario: "opt-out",
input: reflect.StructField{
Anonymous: true,
Tag: "jsonapi:\"-\"",
Type: structType,
},
expectedRes: false,
},
test{
scenario: "wrong type",
input: reflect.StructField{Anonymous: true, Type: stringType},
expectedRes: false,
},
test{
scenario: "not embedded",
input: reflect.StructField{Type: structType},
expectedRes: false,
},
}
for _, test := range tests {
res := (isEmbeddedStruct(test.input) &&
!shouldIgnoreField(test.input.Tag.Get(annotationJSONAPI)))
if res != test.expectedRes {
t.Errorf(
"Scenario -> %s\nGot -> %v\nExpected -> %v\n",
test.scenario, res, test.expectedRes)
}
}
}
// TestEmbeddedUnmarshalOrder tests the behavior of the marshaler/unmarshaler of
// embedded structs when a struct has an embedded struct w/ competing
// attributes, the top-level attributes take precedence it compares the behavior
// against the standard json package
func TestEmbeddedUnmarshalOrder(t *testing.T) {
type Bar struct {
Name int `jsonapi:"attr,Name"`
}
type Foo struct {
Bar
ID string `jsonapi:"primary,foos"`
Name string `jsonapi:"attr,Name"`
}
f := &Foo{
ID: "1",
Name: "foo",
Bar: Bar{
Name: 5,
},
}
// marshal f (Foo) using jsonapi marshaler
jsonAPIData := bytes.NewBuffer(nil)
if err := MarshalPayload(jsonAPIData, f); err != nil {
t.Fatal(err)
}
// marshal f (Foo) using json marshaler
jsonData, err := json.Marshal(f)
if err != nil {
t.Fatal(err)
}
// convert bytes to map[string]interface{} so that we can do a semantic JSON
// comparison
var jsonAPIVal, jsonVal map[string]interface{}
if err = json.Unmarshal(jsonAPIData.Bytes(), &jsonAPIVal); err != nil {
t.Fatal(err)
}
if err = json.Unmarshal(jsonData, &jsonVal); err != nil {
t.Fatal(err)
}
// get to the jsonapi attribute map
jDataMap, ok := jsonAPIVal["data"].(map[string]interface{})
if !ok {
t.Fatal("Could not parse `data`")
}
jAttrMap, ok := jDataMap["attributes"].(map[string]interface{})
if !ok {
t.Fatal("Could not parse `attributes`")
}
// compare
if !reflect.DeepEqual(jAttrMap["Name"], jsonVal["Name"]) {
t.Errorf("Got\n%s\nExpected\n%s\n", jAttrMap["Name"], jsonVal["Name"])
}
}
// TestEmbeddedMarshalOrder tests the behavior of the marshaler/unmarshaler of
// embedded structs when a struct has an embedded struct w/ competing
// attributes, the top-level attributes take precedence it compares the
// behavior against the standard json package
func TestEmbeddedMarshalOrder(t *testing.T) {
type Bar struct {
Name int `jsonapi:"attr,Name"`
}
type Foo struct {
Bar
ID string `jsonapi:"primary,foos"`
Name string `jsonapi:"attr,Name"`
}
// get a jsonapi payload w/ Name attribute of an int type
payloadWithInt, err := json.Marshal(&OnePayload{
Data: &Node{
Type: "foos",
ID: "1",
Attributes: map[string]interface{}{
"Name": 5,
},
},
})
if err != nil {
t.Fatal(err)
}
// get a jsonapi payload w/ Name attribute of an string type
payloadWithString, err := json.Marshal(&OnePayload{
Data: &Node{
Type: "foos",
ID: "1",
Attributes: map[string]interface{}{
"Name": "foo",
},
},
})
if err != nil {
t.Fatal(err)
}
// unmarshal payloadWithInt to f (Foo) using jsonapi unmarshaler; expecting an error
f := &Foo{}
if err = UnmarshalPayload(bytes.NewReader(payloadWithInt), f); err == nil {
t.Errorf("expected an error: int value of 5 should attempt to map to Foo.Name (string) and error")
}
// unmarshal payloadWithString to f (Foo) using jsonapi unmarshaler; expecting no error
f = &Foo{}
if err = UnmarshalPayload(bytes.NewReader(payloadWithString), f); err != nil {
t.Error(err)
}
if f.Name != "foo" {
t.Errorf("Got\n%s\nExpected\n%s\n", "foo", f.Name)
}
// get a json payload w/ Name attribute of an int type
bWithInt, err := json.Marshal(map[string]interface{}{
"Name": 5,
})
if err != nil {
t.Fatal(err)
}
// get a json payload w/ Name attribute of an string type
bWithString, err := json.Marshal(map[string]interface{}{
"Name": "foo",
})
if err != nil {
t.Fatal(err)
}
// unmarshal bWithInt to f (Foo) using json unmarshaler; expecting an error
f = &Foo{}
if err := json.Unmarshal(bWithInt, f); err == nil {
t.Errorf("expected an error: int value of 5 should attempt to map to Foo.Name (string) and error")
}
// unmarshal bWithString to f (Foo) using json unmarshaler; expecting no error
f = &Foo{}
if err := json.Unmarshal(bWithString, f); err != nil {
t.Error(err)
}
if f.Name != "foo" {
t.Errorf("Got\n%s\nExpected\n%s\n", "foo", f.Name)
}
}
func TestMarshalUnmarshalCompositeStruct(t *testing.T) {
type Thing struct {
ID int `jsonapi:"primary,things"`
Fizz string `jsonapi:"attr,fizz"`
Buzz int `jsonapi:"attr,buzz"`
}
type Model struct {
Thing
Foo string `jsonapi:"attr,foo"`
Bar string `jsonapi:"attr,bar"`
Bat string `jsonapi:"attr,bat"`
}
type test struct {
name string
payload *OnePayload
dst, expected interface{}
}
scenarios := []test{}
scenarios = append(scenarios, test{
name: "Model embeds Thing, models have no annotation overlaps",
dst: &Model{},
payload: &OnePayload{
Data: &Node{
Type: "things",
ID: "1",
Attributes: map[string]interface{}{
"bar": "barry",
"bat": "batty",
"buzz": 99,
"fizz": "fizzy",
"foo": "fooey",
},
},
},
expected: &Model{
Foo: "fooey",
Bar: "barry",
Bat: "batty",
Thing: Thing{
ID: 1,
Fizz: "fizzy",
Buzz: 99,
},
},
})
{
type Model struct {
Thing
Foo string `jsonapi:"attr,foo"`
Bar string `jsonapi:"attr,bar"`
Bat string `jsonapi:"attr,bat"`
Buzz int `jsonapi:"attr,buzz"` // overrides Thing.Buzz
}
scenarios = append(scenarios, test{
name: "Model embeds Thing, overlap Buzz attribute",
dst: &Model{},
payload: &OnePayload{
Data: &Node{
Type: "things",
ID: "1",
Attributes: map[string]interface{}{
"bar": "barry",
"bat": "batty",
"buzz": 99,
"fizz": "fizzy",
"foo": "fooey",
},
},
},
expected: &Model{
Foo: "fooey",
Bar: "barry",
Bat: "batty",
Buzz: 99,
Thing: Thing{
ID: 1,
Fizz: "fizzy",
},
},
})
}
{
type Model struct {
Thing
ModelID int `jsonapi:"primary,models"` //overrides Thing.ID due to primary annotation
Foo string `jsonapi:"attr,foo"`
Bar string `jsonapi:"attr,bar"`
Bat string `jsonapi:"attr,bat"`
Buzz int `jsonapi:"attr,buzz"` // overrides Thing.Buzz
}
scenarios = append(scenarios, test{
name: "Model embeds Thing, attribute, and primary annotation overlap",
dst: &Model{},
payload: &OnePayload{
Data: &Node{
Type: "models",
ID: "1",
Attributes: map[string]interface{}{
"bar": "barry",
"bat": "batty",
"buzz": 99,
"fizz": "fizzy",
"foo": "fooey",
},
},
},
expected: &Model{
ModelID: 1,
Foo: "fooey",
Bar: "barry",
Bat: "batty",
Buzz: 99,
Thing: Thing{
Fizz: "fizzy",
},
},
})
}
{
type Model struct {
Thing `jsonapi:"-"`
ModelID int `jsonapi:"primary,models"`
Foo string `jsonapi:"attr,foo"`
Bar string `jsonapi:"attr,bar"`
Bat string `jsonapi:"attr,bat"`
Buzz int `jsonapi:"attr,buzz"`
}
scenarios = append(scenarios, test{
name: "Model embeds Thing, but is annotated w/ ignore",
dst: &Model{},
payload: &OnePayload{
Data: &Node{
Type: "models",
ID: "1",
Attributes: map[string]interface{}{
"bar": "barry",
"bat": "batty",
"buzz": 99,
"foo": "fooey",
},
},
},
expected: &Model{
ModelID: 1,
Foo: "fooey",
Bar: "barry",
Bat: "batty",
Buzz: 99,
},
})
}
{
type Model struct {
*Thing
ModelID int `jsonapi:"primary,models"`
Foo string `jsonapi:"attr,foo"`
Bar string `jsonapi:"attr,bar"`
Bat string `jsonapi:"attr,bat"`
}
scenarios = append(scenarios, test{
name: "Model embeds pointer of Thing; Thing is initialized in advance",
dst: &Model{Thing: &Thing{}},
payload: &OnePayload{
Data: &Node{
Type: "models",
ID: "1",
Attributes: map[string]interface{}{
"bar": "barry",
"bat": "batty",
"foo": "fooey",
"buzz": 99,
"fizz": "fizzy",
},
},
},
expected: &Model{
Thing: &Thing{
Fizz: "fizzy",
Buzz: 99,
},
ModelID: 1,
Foo: "fooey",
Bar: "barry",
Bat: "batty",
},
})
}
{
type Model struct {
*Thing
ModelID int `jsonapi:"primary,models"`
Foo string `jsonapi:"attr,foo"`
Bar string `jsonapi:"attr,bar"`
Bat string `jsonapi:"attr,bat"`
}
scenarios = append(scenarios, test{
name: "Model embeds pointer of Thing; Thing is initialized w/ Unmarshal",
dst: &Model{},
payload: &OnePayload{
Data: &Node{
Type: "models",
ID: "1",
Attributes: map[string]interface{}{
"bar": "barry",
"bat": "batty",
"foo": "fooey",
"buzz": 99,
"fizz": "fizzy",
},
},
},
expected: &Model{
Thing: &Thing{
Fizz: "fizzy",
Buzz: 99,
},
ModelID: 1,
Foo: "fooey",
Bar: "barry",
Bat: "batty",
},
})
}
{
type Model struct {
*Thing
ModelID int `jsonapi:"primary,models"`
Foo string `jsonapi:"attr,foo"`
Bar string `jsonapi:"attr,bar"`
Bat string `jsonapi:"attr,bat"`
}
scenarios = append(scenarios, test{
name: "Model embeds pointer of Thing; jsonapi model doesn't assign anything to Thing; *Thing is nil",
dst: &Model{},
payload: &OnePayload{
Data: &Node{
Type: "models",
ID: "1",
Attributes: map[string]interface{}{
"bar": "barry",
"bat": "batty",
"foo": "fooey",
},
},
},
expected: &Model{
ModelID: 1,
Foo: "fooey",
Bar: "barry",
Bat: "batty",
},
})
}
{
type Model struct {
*Thing
ModelID int `jsonapi:"primary,models"`
Foo string `jsonapi:"attr,foo"`
Bar string `jsonapi:"attr,bar"`
Bat string `jsonapi:"attr,bat"`
}
scenarios = append(scenarios, test{
name: "Model embeds pointer of Thing; *Thing is nil",
dst: &Model{},
payload: &OnePayload{
Data: &Node{
Type: "models",
ID: "1",
Attributes: map[string]interface{}{
"bar": "barry",
"bat": "batty",
"foo": "fooey",
},
},
},
expected: &Model{
ModelID: 1,
Foo: "fooey",
Bar: "barry",
Bat: "batty",
},
})
}
for _, scenario := range scenarios {
t.Logf("running scenario: %s\n", scenario.name)
// get the expected model and marshal to jsonapi
buf := bytes.NewBuffer(nil)
if err := MarshalPayload(buf, scenario.expected); err != nil {
t.Fatal(err)
}
// get the node model representation and marshal to jsonapi
payload, err := json.Marshal(scenario.payload)
if err != nil {
t.Fatal(err)
}
// assert that we're starting w/ the same payload
isJSONEqual, err := isJSONEqual(payload, buf.Bytes())
if err != nil {
t.Fatal(err)
}
if !isJSONEqual {
t.Errorf("Got\n%s\nExpected\n%s\n", buf.Bytes(), payload)
}
// run jsonapi unmarshal
if err := UnmarshalPayload(bytes.NewReader(payload), scenario.dst); err != nil {
t.Fatal(err)
}
// assert decoded and expected models are equal
if !reflect.DeepEqual(scenario.expected, scenario.dst) {
t.Errorf("Got\n%#v\nExpected\n%#v\n", scenario.dst, scenario.expected)
}
}
}
func TestMarshal_duplicatePrimaryAnnotationFromEmbeddedStructs(t *testing.T) {
type Outer struct {
ID string `jsonapi:"primary,outer"`
Comment
*Post
}
o := Outer{
ID: "outer",
Comment: Comment{ID: 1},
Post: &Post{ID: 5},
}
var payloadData map[string]interface{}
// Test the standard libraries JSON handling of dup (ID) fields - it uses
// the Outer's ID
jsonData, err := json.Marshal(o)
if err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(jsonData, &payloadData); err != nil {
t.Fatal(err)
}
if e, a := o.ID, payloadData["ID"]; e != a {
t.Fatalf("Was expecting ID to be %v, got %v", e, a)
}
// Test the JSONAPI lib handling of dup (ID) fields
jsonAPIData := new(bytes.Buffer)
if err := MarshalPayload(jsonAPIData, &o); err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(jsonAPIData.Bytes(), &payloadData); err != nil {
t.Fatal(err)
}
data := payloadData["data"].(map[string]interface{})
id := data["id"].(string)
if e, a := o.ID, id; e != a {
t.Fatalf("Was expecting ID to be %v, got %v", e, a)
}
}
func TestMarshal_duplicateAttributeAnnotationFromEmbeddedStructs(t *testing.T) {
type Foo struct {
Count uint `json:"count" jsonapi:"attr,count"`
}
type Bar struct {
Count uint `json:"count" jsonapi:"attr,count"`
}
type Outer struct {
ID uint `json:"id" jsonapi:"primary,outer"`
Foo
Bar
}
o := Outer{
ID: 1,
Foo: Foo{Count: 1},
Bar: Bar{Count: 2},
}
var payloadData map[string]interface{}
// The standard JSON lib will not serialize either embedded struct's fields if
// a duplicate is encountered
jsonData, err := json.Marshal(o)
if err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(jsonData, &payloadData); err != nil {
t.Fatal(err)
}
if _, found := payloadData["count"]; found {
t.Fatalf("Was not expecting to find the `count` key in the JSON")
}
// Test the JSONAPI lib handling of dup (attr) fields
jsonAPIData := new(bytes.Buffer)
if err := MarshalPayload(jsonAPIData, &o); err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(jsonAPIData.Bytes(), &payloadData); err != nil {
t.Fatal(err)
}
data := payloadData["data"].(map[string]interface{})
if _, found := data["attributes"]; found {
t.Fatal("Was not expecting to find any `attributes` in the JSON API")
}
}
func TestMarshal_duplicateAttributeAnnotationFromEmbeddedStructsPtrs(t *testing.T) {
type Foo struct {
Count uint `json:"count" jsonapi:"attr,count"`
}
type Bar struct {
Count uint `json:"count" jsonapi:"attr,count"`
}
type Outer struct {
ID uint `json:"id" jsonapi:"primary,outer"`
*Foo
*Bar
}
o := Outer{
ID: 1,
Foo: &Foo{Count: 1},
Bar: &Bar{Count: 2},
}
var payloadData map[string]interface{}
// The standard JSON lib will not serialize either embedded struct's fields if
// a duplicate is encountered
jsonData, err := json.Marshal(o)
if err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(jsonData, &payloadData); err != nil {
t.Fatal(err)
}
if _, found := payloadData["count"]; found {
t.Fatalf("Was not expecting to find the `count` key in the JSON")
}
// Test the JSONAPI lib handling of dup (attr) fields
jsonAPIData := new(bytes.Buffer)
if err := MarshalPayload(jsonAPIData, &o); err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(jsonAPIData.Bytes(), &payloadData); err != nil {
t.Fatal(err)
}
data := payloadData["data"].(map[string]interface{})
if _, found := data["attributes"]; found {
t.Fatal("Was not expecting to find any `attributes` in the JSON API")
}
}
func TestMarshal_duplicateAttributeAnnotationFromEmbeddedStructsMixed(t *testing.T) {
type Foo struct {
Count uint `json:"count" jsonapi:"attr,count"`
}
type Bar struct {
Count uint `json:"count" jsonapi:"attr,count"`
}
type Outer struct {
ID uint `json:"id" jsonapi:"primary,outer"`
*Foo
Bar
}
o := Outer{
ID: 1,
Foo: &Foo{Count: 1},
Bar: Bar{Count: 2},
}
var payloadData map[string]interface{}
// The standard JSON lib will not serialize either embedded struct's fields if
// a duplicate is encountered
jsonData, err := json.Marshal(o)
if err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(jsonData, &payloadData); err != nil {
t.Fatal(err)
}
if _, found := payloadData["count"]; found {
t.Fatalf("Was not expecting to find the `count` key in the JSON")
}
// Test the JSONAPI lib handling of dup (attr) fields; it should serialize
// neither
jsonAPIData := new(bytes.Buffer)
if err := MarshalPayload(jsonAPIData, &o); err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(jsonAPIData.Bytes(), &payloadData); err != nil {
t.Fatal(err)
}
data := payloadData["data"].(map[string]interface{})
if _, found := data["attributes"]; found {
t.Fatal("Was not expecting to find any `attributes` in the JSON API")
}
}
func TestMarshal_duplicateFieldFromEmbeddedStructs_serializationNameDiffers(t *testing.T) {
type Foo struct {
Count uint `json:"foo-count" jsonapi:"attr,foo-count"`
}
type Bar struct {
Count uint `json:"bar-count" jsonapi:"attr,bar-count"`
}
type Outer struct {
ID uint `json:"id" jsonapi:"primary,outer"`
Foo
Bar
}
o := Outer{
ID: 1,
Foo: Foo{Count: 1},
Bar: Bar{Count: 2},
}
var payloadData map[string]interface{}
// The standard JSON lib will both the fields since their annotation name
// differs
jsonData, err := json.Marshal(o)
if err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(jsonData, &payloadData); err != nil {
t.Fatal(err)
}
fooJSON, fooFound := payloadData["foo-count"]
if !fooFound {
t.Fatal("Was expecting to find the `foo-count` key in the JSON")
}
if e, a := o.Foo.Count, fooJSON.(float64); e != uint(a) {
t.Fatalf("Was expecting the `foo-count` value to be %v, got %v", e, a)
}
barJSON, barFound := payloadData["bar-count"]
if !barFound {
t.Fatal("Was expecting to find the `bar-count` key in the JSON")
}
if e, a := o.Bar.Count, barJSON.(float64); e != uint(a) {
t.Fatalf("Was expecting the `bar-count` value to be %v, got %v", e, a)
}
// Test the JSONAPI lib handling; it should serialize both
jsonAPIData := new(bytes.Buffer)
if err := MarshalPayload(jsonAPIData, &o); err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(jsonAPIData.Bytes(), &payloadData); err != nil {
t.Fatal(err)
}
data := payloadData["data"].(map[string]interface{})
attributes := data["attributes"].(map[string]interface{})
fooJSONAPI, fooFound := attributes["foo-count"]
if !fooFound {
t.Fatal("Was expecting to find the `foo-count` attribute in the JSON API")
}
if e, a := o.Foo.Count, fooJSONAPI.(float64); e != uint(e) {
t.Fatalf("Was expecting the `foo-count` attrobute to be %v, got %v", e, a)
}
barJSONAPI, barFound := attributes["bar-count"]
if !barFound {
t.Fatal("Was expecting to find the `bar-count` attribute in the JSON API")
}
if e, a := o.Bar.Count, barJSONAPI.(float64); e != uint(e) {
t.Fatalf("Was expecting the `bar-count` attrobute to be %v, got %v", e, a)
}
}
func TestMarshal_embeddedStruct_providesDuplicateAttr(t *testing.T) {
type Foo struct {
Number uint `json:"count" jsonapi:"attr,count"`
}
type Outer struct {
Foo
ID uint `json:"id" jsonapi:"primary,outer"`
Count uint `json:"count" jsonapi:"attr,count"`
}
o := Outer{
ID: 1,
Count: 1,
Foo: Foo{Number: 5},
}
var payloadData map[string]interface{}
// The standard JSON lib will take the count annotated field from the Outer
jsonData, err := json.Marshal(o)
if err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(jsonData, &payloadData); err != nil {
t.Fatal(err)
}
if e, a := o.Count, payloadData["count"].(float64); e != uint(a) {
t.Fatalf("Was expecting a JSON `count` of %v, got %v", e, a)
}
// In JSON API the handling should be that the Outer annotated count field is
// serialized into `attributes`
jsonAPIData := new(bytes.Buffer)
if err := MarshalPayload(jsonAPIData, &o); err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(jsonAPIData.Bytes(), &payloadData); err != nil {
t.Fatal(err)
}
data := payloadData["data"].(map[string]interface{})
attributes := data["attributes"].(map[string]interface{})
if e, a := o.Count, attributes["count"].(float64); e != uint(a) {
t.Fatalf("Was expecting a JSON API `count` attribute of %v, got %v", e, a)
}
}
func TestMarshal_embeddedStructPtr_providesDuplicateAttr(t *testing.T) {
type Foo struct {
Number uint `json:"count" jsonapi:"attr,count"`
}
type Outer struct {
*Foo
ID uint `json:"id" jsonapi:"primary,outer"`
Count uint `json:"count" jsonapi:"attr,count"`
}
o := Outer{
ID: 1,
Count: 1,
Foo: &Foo{Number: 5},
}
var payloadData map[string]interface{}
// The standard JSON lib will take the count annotated field from the Outer
jsonData, err := json.Marshal(o)
if err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(jsonData, &payloadData); err != nil {
t.Fatal(err)
}
if e, a := o.Count, payloadData["count"].(float64); e != uint(a) {
t.Fatalf("Was expecting a JSON `count` of %v, got %v", e, a)
}
// In JSON API the handling should be that the Outer annotated count field is
// serialized into `attributes`
jsonAPIData := new(bytes.Buffer)
if err := MarshalPayload(jsonAPIData, &o); err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(jsonAPIData.Bytes(), &payloadData); err != nil {
t.Fatal(err)
}
data := payloadData["data"].(map[string]interface{})
attributes := data["attributes"].(map[string]interface{})
if e, a := o.Count, attributes["count"].(float64); e != uint(a) {
t.Fatalf("Was expecting a JSON API `count` attribute of %v, got %v", e, a)
}
}
// TODO: test permutation of relations with embedded structs