forked from Mirrors/jsonapi
Merge pull request #99 from Slemgrim/master
Unmarshalling of nested structs
This commit is contained in:
commit
bdc73a22a3
|
@ -155,3 +155,24 @@ func (bc *BadComment) JSONAPILinks() *Links {
|
|||
"self": []string{"invalid", "should error"},
|
||||
}
|
||||
}
|
||||
|
||||
type Company struct {
|
||||
ID string `jsonapi:"primary,companies"`
|
||||
Name string `jsonapi:"attr,name"`
|
||||
Boss Employee `jsonapi:"attr,boss"`
|
||||
Teams []Team `jsonapi:"attr,teams"`
|
||||
FoundedAt time.Time `jsonapi:"attr,founded-at,iso8601"`
|
||||
}
|
||||
|
||||
type Team struct {
|
||||
Name string `jsonapi:"attr,name"`
|
||||
Leader *Employee `jsonapi:"attr,leader"`
|
||||
Members []Employee `jsonapi:"attr,members"`
|
||||
}
|
||||
|
||||
type Employee struct {
|
||||
Firstname string `jsonapi:"attr,firstname"`
|
||||
Surname string `jsonapi:"attr,surname"`
|
||||
Age int `jsonapi:"attr,age"`
|
||||
HiredAt *time.Time `jsonapi:"attr,hired-at,iso8601"`
|
||||
}
|
||||
|
|
533
request.go
533
request.go
|
@ -13,7 +13,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
unsuportedStructTagMsg = "Unsupported jsonapi tag annotation, %s"
|
||||
unsupportedStructTagMsg = "Unsupported jsonapi tag annotation, %s"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -27,13 +27,35 @@ var (
|
|||
// (numeric) but the Struct field was a non numeric type (i.e. not int, uint,
|
||||
// float, etc)
|
||||
ErrUnknownFieldNumberType = errors.New("The struct field was not of a known number type")
|
||||
// ErrUnsupportedPtrType is returned when the Struct field was a pointer but
|
||||
// the JSON value was of a different type
|
||||
ErrUnsupportedPtrType = errors.New("Pointer type in struct is not supported")
|
||||
// ErrInvalidType is returned when the given type is incompatible with the expected type.
|
||||
ErrInvalidType = errors.New("Invalid type provided") // I wish we used punctuation.
|
||||
|
||||
)
|
||||
|
||||
// ErrUnsupportedPtrType is returned when the Struct field was a pointer but
|
||||
// the JSON value was of a different type
|
||||
type ErrUnsupportedPtrType struct {
|
||||
rf reflect.Value
|
||||
t reflect.Type
|
||||
structField reflect.StructField
|
||||
}
|
||||
|
||||
func (eupt ErrUnsupportedPtrType) Error() string {
|
||||
typeName := eupt.t.Elem().Name()
|
||||
kind := eupt.t.Elem().Kind()
|
||||
if kind.String() != "" && kind.String() != typeName {
|
||||
typeName = fmt.Sprintf("%s (%s)", typeName, kind.String())
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"jsonapi: Can't unmarshal %+v (%s) to struct field `%s`, which is a pointer to `%s`",
|
||||
eupt.rf, eupt.rf.Type().Kind(), eupt.structField.Name, typeName,
|
||||
)
|
||||
}
|
||||
|
||||
func newErrUnsupportedPtrType(rf reflect.Value, t reflect.Type, structField reflect.StructField) error {
|
||||
return ErrUnsupportedPtrType{rf, t, structField}
|
||||
}
|
||||
|
||||
// UnmarshalPayload converts an io into a struct instance using jsonapi tags on
|
||||
// struct fields. This method supports single request payloads only, at the
|
||||
// moment. Bulk creates and updates are not supported yet.
|
||||
|
@ -125,7 +147,7 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node)
|
|||
}()
|
||||
|
||||
modelValue := model.Elem()
|
||||
modelType := model.Type().Elem()
|
||||
modelType := modelValue.Type()
|
||||
|
||||
var er error
|
||||
|
||||
|
@ -139,7 +161,6 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node)
|
|||
fieldValue := modelValue.Field(i)
|
||||
|
||||
args := strings.Split(tag, ",")
|
||||
|
||||
if len(args) < 1 {
|
||||
er = ErrBadJSONAPIStructTag
|
||||
break
|
||||
|
@ -196,39 +217,8 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node)
|
|||
|
||||
// Convert the numeric float to one of the supported ID numeric types
|
||||
// (int[8,16,32,64] or uint[8,16,32,64])
|
||||
var idValue reflect.Value
|
||||
switch kind {
|
||||
case reflect.Int:
|
||||
n := int(floatValue)
|
||||
idValue = reflect.ValueOf(&n)
|
||||
case reflect.Int8:
|
||||
n := int8(floatValue)
|
||||
idValue = reflect.ValueOf(&n)
|
||||
case reflect.Int16:
|
||||
n := int16(floatValue)
|
||||
idValue = reflect.ValueOf(&n)
|
||||
case reflect.Int32:
|
||||
n := int32(floatValue)
|
||||
idValue = reflect.ValueOf(&n)
|
||||
case reflect.Int64:
|
||||
n := int64(floatValue)
|
||||
idValue = reflect.ValueOf(&n)
|
||||
case reflect.Uint:
|
||||
n := uint(floatValue)
|
||||
idValue = reflect.ValueOf(&n)
|
||||
case reflect.Uint8:
|
||||
n := uint8(floatValue)
|
||||
idValue = reflect.ValueOf(&n)
|
||||
case reflect.Uint16:
|
||||
n := uint16(floatValue)
|
||||
idValue = reflect.ValueOf(&n)
|
||||
case reflect.Uint32:
|
||||
n := uint32(floatValue)
|
||||
idValue = reflect.ValueOf(&n)
|
||||
case reflect.Uint64:
|
||||
n := uint64(floatValue)
|
||||
idValue = reflect.ValueOf(&n)
|
||||
default:
|
||||
idValue, err := handleNumeric(floatValue, fieldType.Type, fieldValue)
|
||||
if err != nil {
|
||||
// We had a JSON float (numeric), but our field was not one of the
|
||||
// allowed numeric types
|
||||
er = ErrBadJSONAPIID
|
||||
|
@ -244,212 +234,27 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node)
|
|||
fieldValue.Set(reflect.ValueOf(data.ClientID))
|
||||
} else if annotation == annotationAttribute {
|
||||
attributes := data.Attributes
|
||||
|
||||
if attributes == nil || len(data.Attributes) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var iso8601 bool
|
||||
|
||||
if len(args) > 2 {
|
||||
for _, arg := range args[2:] {
|
||||
if arg == annotationISO8601 {
|
||||
iso8601 = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val := attributes[args[1]]
|
||||
attribute := attributes[args[1]]
|
||||
|
||||
// continue if the attribute was not included in the request
|
||||
if val == nil {
|
||||
if attribute == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(val)
|
||||
|
||||
// Handle field of type time.Time
|
||||
if fieldValue.Type() == reflect.TypeOf(time.Time{}) {
|
||||
if iso8601 {
|
||||
var tm string
|
||||
if v.Kind() == reflect.String {
|
||||
tm = v.Interface().(string)
|
||||
} else {
|
||||
er = ErrInvalidISO8601
|
||||
break
|
||||
}
|
||||
|
||||
t, err := time.Parse(iso8601TimeFormat, tm)
|
||||
if err != nil {
|
||||
er = ErrInvalidISO8601
|
||||
break
|
||||
}
|
||||
|
||||
fieldValue.Set(reflect.ValueOf(t))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
var at int64
|
||||
|
||||
if v.Kind() == reflect.Float64 {
|
||||
at = int64(v.Interface().(float64))
|
||||
} else if v.Kind() == reflect.Int {
|
||||
at = v.Int()
|
||||
} else {
|
||||
return ErrInvalidTime
|
||||
}
|
||||
|
||||
t := time.Unix(at, 0)
|
||||
|
||||
fieldValue.Set(reflect.ValueOf(t))
|
||||
|
||||
continue
|
||||
structField := fieldType
|
||||
value, err := unmarshalAttribute(attribute, args, structField, fieldValue)
|
||||
if err != nil {
|
||||
er = err
|
||||
break
|
||||
}
|
||||
|
||||
if fieldValue.Type() == reflect.TypeOf([]string{}) {
|
||||
values := make([]string, v.Len())
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
values[i] = v.Index(i).Interface().(string)
|
||||
}
|
||||
|
||||
fieldValue.Set(reflect.ValueOf(values))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if fieldValue.Type() == reflect.TypeOf(new(time.Time)) {
|
||||
if iso8601 {
|
||||
var tm string
|
||||
if v.Kind() == reflect.String {
|
||||
tm = v.Interface().(string)
|
||||
} else {
|
||||
er = ErrInvalidISO8601
|
||||
break
|
||||
}
|
||||
|
||||
v, err := time.Parse(iso8601TimeFormat, tm)
|
||||
if err != nil {
|
||||
er = ErrInvalidISO8601
|
||||
break
|
||||
}
|
||||
|
||||
t := &v
|
||||
|
||||
fieldValue.Set(reflect.ValueOf(t))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
var at int64
|
||||
|
||||
if v.Kind() == reflect.Float64 {
|
||||
at = int64(v.Interface().(float64))
|
||||
} else if v.Kind() == reflect.Int {
|
||||
at = v.Int()
|
||||
} else {
|
||||
return ErrInvalidTime
|
||||
}
|
||||
|
||||
v := time.Unix(at, 0)
|
||||
t := &v
|
||||
|
||||
fieldValue.Set(reflect.ValueOf(t))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// JSON value was a float (numeric)
|
||||
if v.Kind() == reflect.Float64 {
|
||||
floatValue := v.Interface().(float64)
|
||||
|
||||
// The field may or may not be a pointer to a numeric; the kind var
|
||||
// will not contain a pointer type
|
||||
var kind reflect.Kind
|
||||
if fieldValue.Kind() == reflect.Ptr {
|
||||
kind = fieldType.Type.Elem().Kind()
|
||||
} else {
|
||||
kind = fieldType.Type.Kind()
|
||||
}
|
||||
|
||||
var numericValue reflect.Value
|
||||
|
||||
switch kind {
|
||||
case reflect.Int:
|
||||
n := int(floatValue)
|
||||
numericValue = reflect.ValueOf(&n)
|
||||
case reflect.Int8:
|
||||
n := int8(floatValue)
|
||||
numericValue = reflect.ValueOf(&n)
|
||||
case reflect.Int16:
|
||||
n := int16(floatValue)
|
||||
numericValue = reflect.ValueOf(&n)
|
||||
case reflect.Int32:
|
||||
n := int32(floatValue)
|
||||
numericValue = reflect.ValueOf(&n)
|
||||
case reflect.Int64:
|
||||
n := int64(floatValue)
|
||||
numericValue = reflect.ValueOf(&n)
|
||||
case reflect.Uint:
|
||||
n := uint(floatValue)
|
||||
numericValue = reflect.ValueOf(&n)
|
||||
case reflect.Uint8:
|
||||
n := uint8(floatValue)
|
||||
numericValue = reflect.ValueOf(&n)
|
||||
case reflect.Uint16:
|
||||
n := uint16(floatValue)
|
||||
numericValue = reflect.ValueOf(&n)
|
||||
case reflect.Uint32:
|
||||
n := uint32(floatValue)
|
||||
numericValue = reflect.ValueOf(&n)
|
||||
case reflect.Uint64:
|
||||
n := uint64(floatValue)
|
||||
numericValue = reflect.ValueOf(&n)
|
||||
case reflect.Float32:
|
||||
n := float32(floatValue)
|
||||
numericValue = reflect.ValueOf(&n)
|
||||
case reflect.Float64:
|
||||
n := floatValue
|
||||
numericValue = reflect.ValueOf(&n)
|
||||
default:
|
||||
return ErrUnknownFieldNumberType
|
||||
}
|
||||
|
||||
assign(fieldValue, numericValue)
|
||||
continue
|
||||
}
|
||||
|
||||
// Field was a Pointer type
|
||||
if fieldValue.Kind() == reflect.Ptr {
|
||||
var concreteVal reflect.Value
|
||||
|
||||
switch cVal := val.(type) {
|
||||
case string:
|
||||
concreteVal = reflect.ValueOf(&cVal)
|
||||
case bool:
|
||||
concreteVal = reflect.ValueOf(&cVal)
|
||||
case complex64:
|
||||
concreteVal = reflect.ValueOf(&cVal)
|
||||
case complex128:
|
||||
concreteVal = reflect.ValueOf(&cVal)
|
||||
case uintptr:
|
||||
concreteVal = reflect.ValueOf(&cVal)
|
||||
default:
|
||||
return ErrUnsupportedPtrType
|
||||
}
|
||||
|
||||
if fieldValue.Type() != concreteVal.Type() {
|
||||
return ErrUnsupportedPtrType
|
||||
}
|
||||
|
||||
fieldValue.Set(concreteVal)
|
||||
continue
|
||||
}
|
||||
|
||||
// As a final catch-all, ensure types line up to avoid a runtime panic.
|
||||
if fieldValue.Kind() != v.Kind() {
|
||||
return ErrInvalidType
|
||||
}
|
||||
fieldValue.Set(reflect.ValueOf(val))
|
||||
assign(fieldValue, value)
|
||||
continue
|
||||
|
||||
} else if annotation == annotationRelation {
|
||||
isSlice := fieldValue.Type().Kind() == reflect.Slice
|
||||
|
@ -522,7 +327,7 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node)
|
|||
}
|
||||
|
||||
} else {
|
||||
er = fmt.Errorf(unsuportedStructTagMsg, annotation)
|
||||
er = fmt.Errorf(unsupportedStructTagMsg, annotation)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -548,3 +353,261 @@ func assign(field, value reflect.Value) {
|
|||
field.Set(reflect.Indirect(value))
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalAttribute(
|
||||
attribute interface{},
|
||||
args []string,
|
||||
structField reflect.StructField,
|
||||
fieldValue reflect.Value) (value reflect.Value, err error) {
|
||||
value = reflect.ValueOf(attribute)
|
||||
fieldType := structField.Type
|
||||
|
||||
// Handle field of type []string
|
||||
if fieldValue.Type() == reflect.TypeOf([]string{}) {
|
||||
value, err = handleStringSlice(attribute)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle field of type time.Time
|
||||
if fieldValue.Type() == reflect.TypeOf(time.Time{}) ||
|
||||
fieldValue.Type() == reflect.TypeOf(new(time.Time)) {
|
||||
value, err = handleTime(attribute, args, fieldValue)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle field of type struct
|
||||
if fieldValue.Type().Kind() == reflect.Struct {
|
||||
value, err = handleStruct(attribute, fieldValue)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle field containing slice of structs
|
||||
if fieldValue.Type().Kind() == reflect.Slice &&
|
||||
reflect.TypeOf(fieldValue.Interface()).Elem().Kind() == reflect.Struct {
|
||||
value, err = handleStructSlice(attribute, fieldValue)
|
||||
return
|
||||
}
|
||||
|
||||
// JSON value was a float (numeric)
|
||||
if value.Kind() == reflect.Float64 {
|
||||
value, err = handleNumeric(attribute, fieldType, fieldValue)
|
||||
return
|
||||
}
|
||||
|
||||
// Field was a Pointer type
|
||||
if fieldValue.Kind() == reflect.Ptr {
|
||||
value, err = handlePointer(attribute, args, fieldType, fieldValue, structField)
|
||||
return
|
||||
}
|
||||
|
||||
// As a final catch-all, ensure types line up to avoid a runtime panic.
|
||||
if fieldValue.Kind() != value.Kind() {
|
||||
err = ErrInvalidType
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func handleStringSlice(attribute interface{}) (reflect.Value, error) {
|
||||
v := reflect.ValueOf(attribute)
|
||||
values := make([]string, v.Len())
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
values[i] = v.Index(i).Interface().(string)
|
||||
}
|
||||
|
||||
return reflect.ValueOf(values), nil
|
||||
}
|
||||
|
||||
func handleTime(attribute interface{}, args []string, fieldValue reflect.Value) (reflect.Value, error) {
|
||||
var isIso8601 bool
|
||||
v := reflect.ValueOf(attribute)
|
||||
|
||||
if len(args) > 2 {
|
||||
for _, arg := range args[2:] {
|
||||
if arg == annotationISO8601 {
|
||||
isIso8601 = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isIso8601 {
|
||||
var tm string
|
||||
if v.Kind() == reflect.String {
|
||||
tm = v.Interface().(string)
|
||||
} else {
|
||||
return reflect.ValueOf(time.Now()), ErrInvalidISO8601
|
||||
}
|
||||
|
||||
t, err := time.Parse(iso8601TimeFormat, tm)
|
||||
if err != nil {
|
||||
return reflect.ValueOf(time.Now()), ErrInvalidISO8601
|
||||
}
|
||||
|
||||
if fieldValue.Kind() == reflect.Ptr {
|
||||
return reflect.ValueOf(&t), nil
|
||||
}
|
||||
|
||||
return reflect.ValueOf(t), nil
|
||||
}
|
||||
|
||||
var at int64
|
||||
|
||||
if v.Kind() == reflect.Float64 {
|
||||
at = int64(v.Interface().(float64))
|
||||
} else if v.Kind() == reflect.Int {
|
||||
at = v.Int()
|
||||
} else {
|
||||
return reflect.ValueOf(time.Now()), ErrInvalidTime
|
||||
}
|
||||
|
||||
t := time.Unix(at, 0)
|
||||
|
||||
return reflect.ValueOf(t), nil
|
||||
}
|
||||
|
||||
func handleNumeric(
|
||||
attribute interface{},
|
||||
fieldType reflect.Type,
|
||||
fieldValue reflect.Value) (reflect.Value, error) {
|
||||
v := reflect.ValueOf(attribute)
|
||||
floatValue := v.Interface().(float64)
|
||||
|
||||
var kind reflect.Kind
|
||||
if fieldValue.Kind() == reflect.Ptr {
|
||||
kind = fieldType.Elem().Kind()
|
||||
} else {
|
||||
kind = fieldType.Kind()
|
||||
}
|
||||
|
||||
var numericValue reflect.Value
|
||||
|
||||
switch kind {
|
||||
case reflect.Int:
|
||||
n := int(floatValue)
|
||||
numericValue = reflect.ValueOf(&n)
|
||||
case reflect.Int8:
|
||||
n := int8(floatValue)
|
||||
numericValue = reflect.ValueOf(&n)
|
||||
case reflect.Int16:
|
||||
n := int16(floatValue)
|
||||
numericValue = reflect.ValueOf(&n)
|
||||
case reflect.Int32:
|
||||
n := int32(floatValue)
|
||||
numericValue = reflect.ValueOf(&n)
|
||||
case reflect.Int64:
|
||||
n := int64(floatValue)
|
||||
numericValue = reflect.ValueOf(&n)
|
||||
case reflect.Uint:
|
||||
n := uint(floatValue)
|
||||
numericValue = reflect.ValueOf(&n)
|
||||
case reflect.Uint8:
|
||||
n := uint8(floatValue)
|
||||
numericValue = reflect.ValueOf(&n)
|
||||
case reflect.Uint16:
|
||||
n := uint16(floatValue)
|
||||
numericValue = reflect.ValueOf(&n)
|
||||
case reflect.Uint32:
|
||||
n := uint32(floatValue)
|
||||
numericValue = reflect.ValueOf(&n)
|
||||
case reflect.Uint64:
|
||||
n := uint64(floatValue)
|
||||
numericValue = reflect.ValueOf(&n)
|
||||
case reflect.Float32:
|
||||
n := float32(floatValue)
|
||||
numericValue = reflect.ValueOf(&n)
|
||||
case reflect.Float64:
|
||||
n := floatValue
|
||||
numericValue = reflect.ValueOf(&n)
|
||||
default:
|
||||
return reflect.Value{}, ErrUnknownFieldNumberType
|
||||
}
|
||||
|
||||
return numericValue, nil
|
||||
}
|
||||
|
||||
func handlePointer(
|
||||
attribute interface{},
|
||||
args []string,
|
||||
fieldType reflect.Type,
|
||||
fieldValue reflect.Value,
|
||||
structField reflect.StructField) (reflect.Value, error) {
|
||||
t := fieldValue.Type()
|
||||
var concreteVal reflect.Value
|
||||
|
||||
switch cVal := attribute.(type) {
|
||||
case string:
|
||||
concreteVal = reflect.ValueOf(&cVal)
|
||||
case bool:
|
||||
concreteVal = reflect.ValueOf(&cVal)
|
||||
case complex64, complex128, uintptr:
|
||||
concreteVal = reflect.ValueOf(&cVal)
|
||||
case map[string]interface{}:
|
||||
var err error
|
||||
concreteVal, err = handleStruct(attribute, fieldValue)
|
||||
if err != nil {
|
||||
return reflect.Value{}, newErrUnsupportedPtrType(
|
||||
reflect.ValueOf(attribute), fieldType, structField)
|
||||
}
|
||||
return concreteVal, err
|
||||
default:
|
||||
return reflect.Value{}, newErrUnsupportedPtrType(
|
||||
reflect.ValueOf(attribute), fieldType, structField)
|
||||
}
|
||||
|
||||
if t != concreteVal.Type() {
|
||||
return reflect.Value{}, newErrUnsupportedPtrType(
|
||||
reflect.ValueOf(attribute), fieldType, structField)
|
||||
}
|
||||
|
||||
return concreteVal, nil
|
||||
}
|
||||
|
||||
func handleStruct(
|
||||
attribute interface{},
|
||||
fieldValue reflect.Value) (reflect.Value, error) {
|
||||
|
||||
data, err := json.Marshal(attribute)
|
||||
if err != nil {
|
||||
return reflect.Value{}, err
|
||||
}
|
||||
|
||||
node := new(Node)
|
||||
if err := json.Unmarshal(data, &node.Attributes); err != nil {
|
||||
return reflect.Value{}, err
|
||||
}
|
||||
|
||||
var model reflect.Value
|
||||
if fieldValue.Kind() == reflect.Ptr {
|
||||
model = reflect.New(fieldValue.Type().Elem())
|
||||
} else {
|
||||
model = reflect.New(fieldValue.Type())
|
||||
}
|
||||
|
||||
if err := unmarshalNode(node, model, nil); err != nil {
|
||||
return reflect.Value{}, err
|
||||
}
|
||||
|
||||
|
||||
return model, nil
|
||||
}
|
||||
|
||||
func handleStructSlice(
|
||||
attribute interface{},
|
||||
fieldValue reflect.Value) (reflect.Value, error) {
|
||||
models := reflect.New(fieldValue.Type()).Elem()
|
||||
dataMap := reflect.ValueOf(attribute).Interface().([]interface{})
|
||||
for _, data := range dataMap {
|
||||
model := reflect.New(fieldValue.Type().Elem()).Elem()
|
||||
|
||||
value, err := handleStruct(data, model)
|
||||
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
models = reflect.Append(models, reflect.Indirect(value))
|
||||
}
|
||||
|
||||
return models, nil
|
||||
}
|
||||
|
|
284
request_test.go
284
request_test.go
|
@ -121,12 +121,12 @@ func TestUnmarshalPayloadWithPointerAttr_AbsentVal(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalToStructWithPointerAttr_BadType(t *testing.T) {
|
||||
func TestUnmarshalToStructWithPointerAttr_BadType_bool(t *testing.T) {
|
||||
out := new(WithPointer)
|
||||
in := map[string]interface{}{
|
||||
"name": true, // This is the wrong type.
|
||||
}
|
||||
expectedErrorMessage := ErrUnsupportedPtrType.Error()
|
||||
expectedErrorMessage := "jsonapi: Can't unmarshal true (bool) to struct field `Name`, which is a pointer to `string`"
|
||||
|
||||
err := UnmarshalPayload(sampleWithPointerPayload(in), out)
|
||||
|
||||
|
@ -136,6 +136,71 @@ func TestUnmarshalToStructWithPointerAttr_BadType(t *testing.T) {
|
|||
if err.Error() != expectedErrorMessage {
|
||||
t.Fatalf("Unexpected error message: %s", err.Error())
|
||||
}
|
||||
if _, ok := err.(ErrUnsupportedPtrType); !ok {
|
||||
t.Fatalf("Unexpected error type: %s", reflect.TypeOf(err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalToStructWithPointerAttr_BadType_MapPtr(t *testing.T) {
|
||||
out := new(WithPointer)
|
||||
in := map[string]interface{}{
|
||||
"name": &map[string]interface{}{"a": 5}, // This is the wrong type.
|
||||
}
|
||||
expectedErrorMessage := "jsonapi: Can't unmarshal map[a:5] (map) to struct field `Name`, which is a pointer to `string`"
|
||||
|
||||
err := UnmarshalPayload(sampleWithPointerPayload(in), out)
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error due to invalid type.")
|
||||
}
|
||||
if err.Error() != expectedErrorMessage {
|
||||
t.Fatalf("Unexpected error message: %s", err.Error())
|
||||
}
|
||||
if _, ok := err.(ErrUnsupportedPtrType); !ok {
|
||||
t.Fatalf("Unexpected error type: %s", reflect.TypeOf(err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalToStructWithPointerAttr_BadType_Struct(t *testing.T) {
|
||||
out := new(WithPointer)
|
||||
type FooStruct struct{ A int }
|
||||
in := map[string]interface{}{
|
||||
"name": FooStruct{A: 5}, // This is the wrong type.
|
||||
}
|
||||
expectedErrorMessage := "jsonapi: Can't unmarshal map[A:5] (map) to struct field `Name`, which is a pointer to `string`"
|
||||
|
||||
err := UnmarshalPayload(sampleWithPointerPayload(in), out)
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error due to invalid type.")
|
||||
}
|
||||
if err.Error() != expectedErrorMessage {
|
||||
t.Fatalf("Unexpected error message: %s", err.Error())
|
||||
}
|
||||
if _, ok := err.(ErrUnsupportedPtrType); !ok {
|
||||
t.Fatalf("Unexpected error type: %s", reflect.TypeOf(err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalToStructWithPointerAttr_BadType_IntSlice(t *testing.T) {
|
||||
out := new(WithPointer)
|
||||
type FooStruct struct{ A, B int }
|
||||
in := map[string]interface{}{
|
||||
"name": []int{4, 5}, // This is the wrong type.
|
||||
}
|
||||
expectedErrorMessage := "jsonapi: Can't unmarshal [4 5] (slice) to struct field `Name`, which is a pointer to `string`"
|
||||
|
||||
err := UnmarshalPayload(sampleWithPointerPayload(in), out)
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error due to invalid type.")
|
||||
}
|
||||
if err.Error() != expectedErrorMessage {
|
||||
t.Fatalf("Unexpected error message: %s", err.Error())
|
||||
}
|
||||
if _, ok := err.(ErrUnsupportedPtrType); !ok {
|
||||
t.Fatalf("Unexpected error type: %s", reflect.TypeOf(err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringPointerField(t *testing.T) {
|
||||
|
@ -945,3 +1010,218 @@ func sampleSerializedEmbeddedTestModel() *Blog {
|
|||
|
||||
return blog
|
||||
}
|
||||
|
||||
func TestUnmarshalNestedStructPtr(t *testing.T) {
|
||||
type Director struct {
|
||||
Firstname string `jsonapi:"attr,firstname"`
|
||||
Surname string `jsonapi:"attr,surname"`
|
||||
}
|
||||
type Movie struct {
|
||||
ID string `jsonapi:"primary,movies"`
|
||||
Name string `jsonapi:"attr,name"`
|
||||
Director *Director `jsonapi:"attr,director"`
|
||||
}
|
||||
sample := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"type": "movies",
|
||||
"id": "123",
|
||||
"attributes": map[string]interface{}{
|
||||
"name": "The Shawshank Redemption",
|
||||
"director": map[string]interface{}{
|
||||
"firstname": "Frank",
|
||||
"surname": "Darabont",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
data, err := json.Marshal(sample)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
in := bytes.NewReader(data)
|
||||
out := new(Movie)
|
||||
|
||||
if err := UnmarshalPayload(in, out); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if out.Name != "The Shawshank Redemption" {
|
||||
t.Fatalf("expected out.Name to be `The Shawshank Redemption`, but got `%s`", out.Name)
|
||||
}
|
||||
if out.Director.Firstname != "Frank" {
|
||||
t.Fatalf("expected out.Director.Firstname to be `Frank`, but got `%s`", out.Director.Firstname)
|
||||
}
|
||||
if out.Director.Surname != "Darabont" {
|
||||
t.Fatalf("expected out.Director.Surname to be `Darabont`, but got `%s`", out.Director.Surname)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalNestedStruct(t *testing.T) {
|
||||
boss := map[string]interface{}{
|
||||
"firstname": "Hubert",
|
||||
"surname": "Farnsworth",
|
||||
"age": 176,
|
||||
"hired-at": "2016-08-17T08:27:12Z",
|
||||
}
|
||||
|
||||
sample := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"type": "companies",
|
||||
"id": "123",
|
||||
"attributes": map[string]interface{}{
|
||||
"name": "Planet Express",
|
||||
"boss": boss,
|
||||
"founded-at": "2016-08-17T08:27:12Z",
|
||||
"teams": []map[string]interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "Dev",
|
||||
"members": []map[string]interface{}{
|
||||
map[string]interface{}{"firstname": "Sean"},
|
||||
map[string]interface{}{"firstname": "Iz"},
|
||||
},
|
||||
"leader": map[string]interface{}{"firstname": "Iz"},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"name": "DxE",
|
||||
"members": []map[string]interface{}{
|
||||
map[string]interface{}{"firstname": "Akshay"},
|
||||
map[string]interface{}{"firstname": "Peri"},
|
||||
},
|
||||
"leader": map[string]interface{}{"firstname": "Peri"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
data, err := json.Marshal(sample)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
in := bytes.NewReader(data)
|
||||
out := new(Company)
|
||||
|
||||
if err := UnmarshalPayload(in, out); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if out.Boss.Firstname != "Hubert" {
|
||||
t.Fatalf("expected `Hubert` at out.Boss.Firstname, but got `%s`", out.Boss.Firstname)
|
||||
}
|
||||
|
||||
if out.Boss.Age != 176 {
|
||||
t.Fatalf("expected `176` at out.Boss.Age, but got `%d`", out.Boss.Age)
|
||||
}
|
||||
|
||||
if out.Boss.HiredAt.IsZero() {
|
||||
t.Fatalf("expected out.Boss.HiredAt to be zero, but got `%t`", out.Boss.HiredAt.IsZero())
|
||||
}
|
||||
|
||||
if len(out.Teams) != 2 {
|
||||
t.Fatalf("expected len(out.Teams) to be 2, but got `%d`", len(out.Teams))
|
||||
}
|
||||
|
||||
if out.Teams[0].Name != "Dev" {
|
||||
t.Fatalf("expected out.Teams[0].Name to be `Dev`, but got `%s`", out.Teams[0].Name)
|
||||
}
|
||||
|
||||
if out.Teams[1].Name != "DxE" {
|
||||
t.Fatalf("expected out.Teams[1].Name to be `DxE`, but got `%s`", out.Teams[1].Name)
|
||||
}
|
||||
|
||||
if len(out.Teams[0].Members) != 2 {
|
||||
t.Fatalf("expected len(out.Teams[0].Members) to be 2, but got `%d`", len(out.Teams[0].Members))
|
||||
}
|
||||
|
||||
if len(out.Teams[1].Members) != 2 {
|
||||
t.Fatalf("expected len(out.Teams[1].Members) to be 2, but got `%d`", len(out.Teams[1].Members))
|
||||
}
|
||||
|
||||
if out.Teams[0].Members[0].Firstname != "Sean" {
|
||||
t.Fatalf("expected out.Teams[0].Members[0].Firstname to be `Sean`, but got `%s`", out.Teams[0].Members[0].Firstname)
|
||||
}
|
||||
|
||||
if out.Teams[0].Members[1].Firstname != "Iz" {
|
||||
t.Fatalf("expected out.Teams[0].Members[1].Firstname to be `Iz`, but got `%s`", out.Teams[0].Members[1].Firstname)
|
||||
}
|
||||
|
||||
if out.Teams[1].Members[0].Firstname != "Akshay" {
|
||||
t.Fatalf("expected out.Teams[1].Members[0].Firstname to be `Akshay`, but got `%s`", out.Teams[1].Members[0].Firstname)
|
||||
}
|
||||
|
||||
if out.Teams[1].Members[1].Firstname != "Peri" {
|
||||
t.Fatalf("expected out.Teams[1].Members[1].Firstname to be `Peri`, but got `%s`", out.Teams[1].Members[1].Firstname)
|
||||
}
|
||||
|
||||
if out.Teams[0].Leader.Firstname != "Iz" {
|
||||
t.Fatalf("expected out.Teams[0].Leader.Firstname to be `Iz`, but got `%s`", out.Teams[0].Leader.Firstname)
|
||||
}
|
||||
|
||||
if out.Teams[1].Leader.Firstname != "Peri" {
|
||||
t.Fatalf("expected out.Teams[1].Leader.Firstname to be `Peri`, but got `%s`", out.Teams[1].Leader.Firstname)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalNestedStructSlice(t *testing.T) {
|
||||
|
||||
fry := map[string]interface{}{
|
||||
"firstname": "Philip J.",
|
||||
"surname": "Fry",
|
||||
"age": 25,
|
||||
"hired-at": "2016-08-17T08:27:12Z",
|
||||
}
|
||||
|
||||
bender := map[string]interface{}{
|
||||
"firstname": "Bender Bending",
|
||||
"surname": "Rodriguez",
|
||||
"age": 19,
|
||||
"hired-at": "2016-08-17T08:27:12Z",
|
||||
}
|
||||
|
||||
deliveryCrew := map[string]interface{}{
|
||||
"name": "Delivery Crew",
|
||||
"members": []interface{}{
|
||||
fry,
|
||||
bender,
|
||||
},
|
||||
}
|
||||
|
||||
sample := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"type": "companies",
|
||||
"id": "123",
|
||||
"attributes": map[string]interface{}{
|
||||
"name": "Planet Express",
|
||||
"teams": []interface{}{
|
||||
deliveryCrew,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
data, err := json.Marshal(sample)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
in := bytes.NewReader(data)
|
||||
out := new(Company)
|
||||
|
||||
if err := UnmarshalPayload(in, out); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if out.Teams[0].Name != "Delivery Crew" {
|
||||
t.Fatalf("Nested struct not unmarshalled: Expected `Delivery Crew` but got `%s`", out.Teams[0].Name)
|
||||
}
|
||||
|
||||
if len(out.Teams[0].Members) != 2 {
|
||||
t.Fatalf("Nested struct not unmarshalled: Expected to have `2` Members but got `%d`",
|
||||
len(out.Teams[0].Members))
|
||||
}
|
||||
|
||||
if out.Teams[0].Members[0].Firstname != "Philip J." {
|
||||
t.Fatalf("Nested struct not unmarshalled: Expected `Philip J.` but got `%s`",
|
||||
out.Teams[0].Members[0].Firstname)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue