diff --git a/request.go b/request.go index fe29706..5f6d5ae 100644 --- a/request.go +++ b/request.go @@ -542,9 +542,29 @@ func fullNode(n *Node, included *map[string]*Node) *Node { // assign will take the value specified and assign it to the field; if // field is expecting a ptr assign will assign a ptr. func assign(field, value reflect.Value) { - if field.Kind() == reflect.Ptr { + if value.Type().AssignableTo(field.Type()) { field.Set(value) + return + } + + indirectValue := reflect.Indirect(value) + if indirectValue.Type().AssignableTo(field.Type()) { + field.Set(indirectValue) + return + } + + // Conversion required. + + if field.Kind() == reflect.Ptr { + if value.Kind() == reflect.Ptr { + field.Set(value.Convert(field.Type())) + } else { + // Because field is zero value, we cannot simply field.Elem().Set(). + v := reflect.New(field.Type().Elem()) + v.Elem().Set(value.Convert(field.Type().Elem())) + field.Set(v) + } } else { - field.Set(reflect.Indirect(value)) + field.Set(reflect.Indirect(value).Convert(field.Type())) } } diff --git a/request_test.go b/request_test.go index 2206449..facf971 100644 --- a/request_test.go +++ b/request_test.go @@ -703,6 +703,112 @@ func TestManyPayload_withLinks(t *testing.T) { } } +func TestUnmarshalPayloadIDTypeOfString(t *testing.T) { + t.Run("Unmarshal string to value type", func(t *testing.T) { + data := map[string]interface{}{ + "data": map[string]interface{}{ + "type": "books", + "id": "978-3-16-148410-0", + "attributes": map[string]interface{}{ + "title": "Gesammelte Werke in deutscher Sprache", + }, + }, + } + b, err := json.Marshal(data) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + type ( + IBSN string + + Book struct { + ID IBSN `jsonapi:"primary,books"` + Title string `jsonapi:"attr,title"` + } + ) + + book := &Book{} + if err := UnmarshalPayload(bytes.NewReader(b), book); err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + expected := IBSN("978-3-16-148410-0") + if book.ID != expected { + t.Fatalf("Expected book id to be %v but got %v", expected, book.ID) + } + }) + + t.Run("Unmarshal string to ptr type", func(t *testing.T) { + data := map[string]interface{}{ + "data": map[string]interface{}{ + "type": "books", + "id": "978-3-16-148410-0", + "attributes": map[string]interface{}{ + "title": "Gesammelte Werke in deutscher Sprache", + }, + }, + } + b, err := json.Marshal(data) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + type ( + IBSN string + + Book struct { + ID *IBSN `jsonapi:"primary,books"` + Title string `jsonapi:"attr,title"` + } + ) + + book := &Book{} + if err := UnmarshalPayload(bytes.NewReader(b), book); err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + expected := IBSN("978-3-16-148410-0") + if !reflect.DeepEqual(book.ID, &expected) { + t.Fatalf("Expected book id to be %v but got %v", &expected, book.ID) + } + }) + + t.Run("Unmarshal nil to ptr type", func(t *testing.T) { + data := map[string]interface{}{ + "data": map[string]interface{}{ + "type": "books", + "id": nil, + "attributes": map[string]interface{}{ + "title": "Gesammelte Werke in deutscher Sprache", + }, + }, + } + b, err := json.Marshal(data) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + type ( + IBSN string + + Book struct { + ID *IBSN `jsonapi:"primary,books"` + Title string `jsonapi:"attr,title"` + } + ) + + book := &Book{} + if err := UnmarshalPayload(bytes.NewReader(b), book); err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + if book.ID != nil { + t.Fatalf("Expected book id to be %v but got %v", nil, book.ID) + } + }) +} + func samplePayloadWithoutIncluded() map[string]interface{} { return map[string]interface{}{ "data": map[string]interface{}{ diff --git a/response.go b/response.go index f6a1b86..fe3dbe1 100644 --- a/response.go +++ b/response.go @@ -251,7 +251,7 @@ func visitModelNode(model interface{}, included *map[string]*Node, // Handle allowed types switch kind { case reflect.String: - node.ID = v.Interface().(string) + node.ID = v.String() case reflect.Int: node.ID = strconv.FormatInt(int64(v.Interface().(int)), 10) case reflect.Int8: diff --git a/response_test.go b/response_test.go index 157f434..9c20ea2 100644 --- a/response_test.go +++ b/response_test.go @@ -279,6 +279,38 @@ func TestMarshalIDPtr(t *testing.T) { } } +func TestMarshalIDTypeOfString(t *testing.T) { + type ( + IBSN string + + Book struct { + ID IBSN `jsonapi:"primary,books"` + } + ) + + book := &Book{ID: IBSN("978-3-16-148410-0")} + + out := &bytes.Buffer{} + if err := MarshalPayload(out, book); err != nil { + t.Fatal(err) + } + + var payload map[string]interface{} + if err := json.Unmarshal(out.Bytes(), &payload); err != nil { + t.Fatal(err) + } + + data := payload["data"].(map[string]interface{}) + id, ok := data["id"] + if !ok { + t.Fatal("Was expecting the data.id value to exist") + } + + if id != "978-3-16-148410-0" { + t.Fatalf("Was expecting the data.id value to be %s but got %s", "978-3-16-148410-0", id) + } +} + func TestMarshalOnePayload_omitIDString(t *testing.T) { type Foo struct { ID string `jsonapi:"primary,foo"`