Merge pull request #6 from shwoodard/shwoodard-slemgrim-pull-99

Use jsonapi struct tags for nested attrs
This commit is contained in:
Markus Ritberger 2018-10-04 18:04:16 +02:00 committed by GitHub
commit 417d4eb8fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 54 additions and 90 deletions

View File

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

View File

@ -13,7 +13,7 @@ import (
)
const (
unsuportedStructTagMsg = "Unsupported jsonapi tag annotation, %s"
unsupportedStructTagMsg = "Unsupported jsonapi tag annotation, %s"
)
var (
@ -147,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
@ -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
// (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
@ -358,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)
}
}
@ -395,33 +364,33 @@ func unmarshalAttribute(
// Handle field of type []string
if fieldValue.Type() == reflect.TypeOf([]string{}) {
value, err = handleStringSlice(attribute, args, fieldType, fieldValue)
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, fieldType, fieldValue)
value, err = handleTime(attribute, args, fieldValue)
return
}
// Handle field of type struct
if fieldValue.Type().Kind() == reflect.Struct {
value, err = handleStruct(attribute, args, fieldType, fieldValue)
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, args, fieldType, fieldValue)
value, err = handleStructSlice(attribute, fieldValue)
return
}
// JSON value was a float (numeric)
if value.Kind() == reflect.Float64 {
value, err = handleNumeric(attribute, args, fieldType, fieldValue)
value, err = handleNumeric(attribute, fieldType, fieldValue)
return
}
@ -440,11 +409,7 @@ func unmarshalAttribute(
return
}
func handleStringSlice(
attribute interface{},
args []string,
fieldType reflect.Type,
fieldValue reflect.Value) (reflect.Value, error) {
func handleStringSlice(attribute interface{}) (reflect.Value, error) {
v := reflect.ValueOf(attribute)
values := make([]string, v.Len())
for i := 0; i < v.Len(); i++ {
@ -454,11 +419,7 @@ func handleStringSlice(
return reflect.ValueOf(values), nil
}
func handleTime(
attribute interface{},
args []string,
fieldType reflect.Type,
fieldValue reflect.Value) (reflect.Value, error) {
func handleTime(attribute interface{}, args []string, fieldValue reflect.Value) (reflect.Value, error) {
var isIso8601 bool
v := reflect.ValueOf(attribute)
@ -507,7 +468,6 @@ func handleTime(
func handleNumeric(
attribute interface{},
args []string,
fieldType reflect.Type,
fieldValue reflect.Value) (reflect.Value, error) {
v := reflect.ValueOf(attribute)
@ -584,12 +544,12 @@ func handlePointer(
concreteVal = reflect.ValueOf(&cVal)
case map[string]interface{}:
var err error
concreteVal, err = handleStruct(attribute, args, fieldType, fieldValue)
concreteVal, err = handleStruct(attribute, fieldValue)
if err != nil {
return reflect.Value{}, newErrUnsupportedPtrType(
reflect.ValueOf(attribute), fieldType, structField)
}
return concreteVal.Elem(), err
return concreteVal, err
default:
return reflect.Value{}, newErrUnsupportedPtrType(
reflect.ValueOf(attribute), fieldType, structField)
@ -605,37 +565,42 @@ func handlePointer(
func handleStruct(
attribute interface{},
args []string,
fieldType reflect.Type,
fieldValue reflect.Value) (reflect.Value, error) {
model := reflect.New(fieldValue.Type())
data, err := json.Marshal(attribute)
if err != nil {
return model, err
return reflect.Value{}, err
}
err = json.Unmarshal(data, model.Interface())
if err != nil {
return model, err
node := new(Node)
if err := json.Unmarshal(data, &node.Attributes); err != nil {
return reflect.Value{}, 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(
attribute interface{},
args []string,
fieldType reflect.Type,
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()
modelType := model.Type()
value, err := handleStruct(data, []string{}, modelType, model)
value, err := handleStruct(data, model)
if err != nil {
continue

View File

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