use jsonapi tags for nested attrs

This commit is contained in:
Sam Woodard 2018-09-28 14:57:05 -07:00
parent 8b7e0bc2c0
commit 3c8221b373
3 changed files with 54 additions and 90 deletions

View File

@ -165,14 +165,14 @@ type Company struct {
} }
type Team struct { type Team struct {
Name string `json:"name"` Name string `jsonapi:"attr,name"`
Leader *Employee `json:"leader"` Leader *Employee `jsonapi:"attr,leader"`
Members []Employee `json:"members"` Members []Employee `jsonapi:"attr,members"`
} }
type Employee struct { type Employee struct {
Firstname string `json:"firstname"` Firstname string `jsonapi:"attr,firstname"`
Surname string `json:"surname"` Surname string `jsonapi:"attr,surname"`
Age int `json:"age"` Age int `jsonapi:"attr,age"`
HiredAt *time.Time `json:"hired-at,iso8601"` HiredAt *time.Time `jsonapi:"attr,hired-at,iso8601"`
} }

View File

@ -13,7 +13,7 @@ import (
) )
const ( const (
unsuportedStructTagMsg = "Unsupported jsonapi tag annotation, %s" unsupportedStructTagMsg = "Unsupported jsonapi tag annotation, %s"
) )
var ( var (
@ -147,7 +147,7 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node)
}() }()
modelValue := model.Elem() modelValue := model.Elem()
modelType := model.Type().Elem() modelType := modelValue.Type()
var er error var er error
@ -217,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 // Convert the numeric float to one of the supported ID numeric types
// (int[8,16,32,64] or uint[8,16,32,64]) // (int[8,16,32,64] or uint[8,16,32,64])
var idValue reflect.Value idValue, err := handleNumeric(floatValue, fieldType.Type, fieldValue)
switch kind { if err != nil {
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:
// We had a JSON float (numeric), but our field was not one of the // We had a JSON float (numeric), but our field was not one of the
// allowed numeric types // allowed numeric types
er = ErrBadJSONAPIID er = ErrBadJSONAPIID
@ -358,7 +327,7 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node)
} }
} else { } else {
er = fmt.Errorf(unsuportedStructTagMsg, annotation) er = fmt.Errorf(unsupportedStructTagMsg, annotation)
} }
} }
@ -395,33 +364,33 @@ func unmarshalAttribute(
// Handle field of type []string // Handle field of type []string
if fieldValue.Type() == reflect.TypeOf([]string{}) { if fieldValue.Type() == reflect.TypeOf([]string{}) {
value, err = handleStringSlice(attribute, args, fieldType, fieldValue) value, err = handleStringSlice(attribute)
return return
} }
// Handle field of type time.Time // Handle field of type time.Time
if fieldValue.Type() == reflect.TypeOf(time.Time{}) || if fieldValue.Type() == reflect.TypeOf(time.Time{}) ||
fieldValue.Type() == reflect.TypeOf(new(time.Time)) { fieldValue.Type() == reflect.TypeOf(new(time.Time)) {
value, err = handleTime(attribute, args, fieldType, fieldValue) value, err = handleTime(attribute, args, fieldValue)
return return
} }
// Handle field of type struct // Handle field of type struct
if fieldValue.Type().Kind() == reflect.Struct { if fieldValue.Type().Kind() == reflect.Struct {
value, err = handleStruct(attribute, args, fieldType, fieldValue) value, err = handleStruct(attribute, fieldValue)
return return
} }
// Handle field containing slice of structs // Handle field containing slice of structs
if fieldValue.Type().Kind() == reflect.Slice && if fieldValue.Type().Kind() == reflect.Slice &&
reflect.TypeOf(fieldValue.Interface()).Elem().Kind() == reflect.Struct { reflect.TypeOf(fieldValue.Interface()).Elem().Kind() == reflect.Struct {
value, err = handleStructSlice(attribute, args, fieldType, fieldValue) value, err = handleStructSlice(attribute, fieldValue)
return return
} }
// JSON value was a float (numeric) // JSON value was a float (numeric)
if value.Kind() == reflect.Float64 { if value.Kind() == reflect.Float64 {
value, err = handleNumeric(attribute, args, fieldType, fieldValue) value, err = handleNumeric(attribute, fieldType, fieldValue)
return return
} }
@ -440,11 +409,7 @@ func unmarshalAttribute(
return return
} }
func handleStringSlice( func handleStringSlice(attribute interface{}) (reflect.Value, error) {
attribute interface{},
args []string,
fieldType reflect.Type,
fieldValue reflect.Value) (reflect.Value, error) {
v := reflect.ValueOf(attribute) v := reflect.ValueOf(attribute)
values := make([]string, v.Len()) values := make([]string, v.Len())
for i := 0; i < v.Len(); i++ { for i := 0; i < v.Len(); i++ {
@ -454,11 +419,7 @@ func handleStringSlice(
return reflect.ValueOf(values), nil return reflect.ValueOf(values), nil
} }
func handleTime( func handleTime(attribute interface{}, args []string, fieldValue reflect.Value) (reflect.Value, error) {
attribute interface{},
args []string,
fieldType reflect.Type,
fieldValue reflect.Value) (reflect.Value, error) {
var isIso8601 bool var isIso8601 bool
v := reflect.ValueOf(attribute) v := reflect.ValueOf(attribute)
@ -507,7 +468,6 @@ func handleTime(
func handleNumeric( func handleNumeric(
attribute interface{}, attribute interface{},
args []string,
fieldType reflect.Type, fieldType reflect.Type,
fieldValue reflect.Value) (reflect.Value, error) { fieldValue reflect.Value) (reflect.Value, error) {
v := reflect.ValueOf(attribute) v := reflect.ValueOf(attribute)
@ -584,12 +544,12 @@ func handlePointer(
concreteVal = reflect.ValueOf(&cVal) concreteVal = reflect.ValueOf(&cVal)
case map[string]interface{}: case map[string]interface{}:
var err error var err error
concreteVal, err = handleStruct(attribute, args, fieldType, fieldValue) concreteVal, err = handleStruct(attribute, fieldValue)
if err != nil { if err != nil {
return reflect.Value{}, newErrUnsupportedPtrType( return reflect.Value{}, newErrUnsupportedPtrType(
reflect.ValueOf(attribute), fieldType, structField) reflect.ValueOf(attribute), fieldType, structField)
} }
return concreteVal.Elem(), err return concreteVal, err
default: default:
return reflect.Value{}, newErrUnsupportedPtrType( return reflect.Value{}, newErrUnsupportedPtrType(
reflect.ValueOf(attribute), fieldType, structField) reflect.ValueOf(attribute), fieldType, structField)
@ -605,37 +565,42 @@ func handlePointer(
func handleStruct( func handleStruct(
attribute interface{}, attribute interface{},
args []string,
fieldType reflect.Type,
fieldValue reflect.Value) (reflect.Value, error) { fieldValue reflect.Value) (reflect.Value, error) {
model := reflect.New(fieldValue.Type())
data, err := json.Marshal(attribute) data, err := json.Marshal(attribute)
if err != nil { if err != nil {
return model, err return reflect.Value{}, err
} }
err = json.Unmarshal(data, model.Interface()) node := new(Node)
if err := json.Unmarshal(data, &node.Attributes); err != nil {
if err != nil { return reflect.Value{}, err
return model, err
} }
return model, 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( func handleStructSlice(
attribute interface{}, attribute interface{},
args []string,
fieldType reflect.Type,
fieldValue reflect.Value) (reflect.Value, error) { fieldValue reflect.Value) (reflect.Value, error) {
models := reflect.New(fieldValue.Type()).Elem() models := reflect.New(fieldValue.Type()).Elem()
dataMap := reflect.ValueOf(attribute).Interface().([]interface{}) dataMap := reflect.ValueOf(attribute).Interface().([]interface{})
for _, data := range dataMap { for _, data := range dataMap {
model := reflect.New(fieldValue.Type().Elem()).Elem() model := reflect.New(fieldValue.Type().Elem()).Elem()
modelType := model.Type()
value, err := handleStruct(data, []string{}, modelType, model) value, err := handleStruct(data, model)
if err != nil { if err != nil {
continue continue

View File

@ -1013,8 +1013,8 @@ func sampleSerializedEmbeddedTestModel() *Blog {
func TestUnmarshalNestedStructPtr(t *testing.T) { func TestUnmarshalNestedStructPtr(t *testing.T) {
type Director struct { type Director struct {
Firstname string `json:"firstname"` Firstname string `jsonapi:"attr,firstname"`
Surname string `json:"surname"` Surname string `jsonapi:"attr,surname"`
} }
type Movie struct { type Movie struct {
ID string `jsonapi:"primary,movies"` ID string `jsonapi:"primary,movies"`
@ -1058,7 +1058,6 @@ func TestUnmarshalNestedStructPtr(t *testing.T) {
} }
func TestUnmarshalNestedStruct(t *testing.T) { func TestUnmarshalNestedStruct(t *testing.T) {
boss := map[string]interface{}{ boss := map[string]interface{}{
"firstname": "Hubert", "firstname": "Hubert",
"surname": "Farnsworth", "surname": "Farnsworth",
@ -1074,22 +1073,22 @@ func TestUnmarshalNestedStruct(t *testing.T) {
"name": "Planet Express", "name": "Planet Express",
"boss": boss, "boss": boss,
"founded-at": "2016-08-17T08:27:12Z", "founded-at": "2016-08-17T08:27:12Z",
"teams": []Team{ "teams": []map[string]interface{}{
Team{ map[string]interface{}{
Name: "Dev", "name": "Dev",
Members: []Employee{ "members": []map[string]interface{}{
Employee{Firstname: "Sean"}, map[string]interface{}{"firstname": "Sean"},
Employee{Firstname: "Iz"}, map[string]interface{}{"firstname": "Iz"},
}, },
Leader: &Employee{Firstname: "Iz"}, "leader": map[string]interface{}{"firstname": "Iz"},
}, },
Team{ map[string]interface{}{
Name: "DxE", "name": "DxE",
Members: []Employee{ "members": []map[string]interface{}{
Employee{Firstname: "Akshay"}, map[string]interface{}{"firstname": "Akshay"},
Employee{Firstname: "Peri"}, map[string]interface{}{"firstname": "Peri"},
}, },
Leader: &Employee{Firstname: "Peri"}, "leader": map[string]interface{}{"firstname": "Peri"},
}, },
}, },
}, },