Skip to content

Commit af21dba

Browse files
committed
handle nested struct slices
1 parent 0c97f0c commit af21dba

File tree

3 files changed

+236
-41
lines changed

3 files changed

+236
-41
lines changed

models_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,24 @@ func (bc *BadComment) JSONAPILinks() *Links {
155155
"self": []string{"invalid", "should error"},
156156
}
157157
}
158+
159+
type Company struct {
160+
ID string `jsonapi:"primary,companies"`
161+
Name string `jsonapi:"attr,name"`
162+
Boss Employee `jsonapi:"attr,boss"`
163+
Teams []Team `jsonapi:"attr,teams"`
164+
FoundedAt time.Time `jsonapi:"attr,founded-at,iso8601"`
165+
}
166+
167+
type Team struct {
168+
Name string `jsonapi:"attr,name"`
169+
Leader *Employee `jsonapi:"attr,leader"`
170+
Members []Employee `jsonapi:"attr,members"`
171+
}
172+
173+
type Employee struct {
174+
Firstname string `jsonapi:"attr,firstname"`
175+
Surname string `jsonapi:"attr,surname"`
176+
Age int `jsonapi:"attr,age"`
177+
HiredAt *time.Time `jsonapi:"attr,hired-at,iso8601"`
178+
}

request.go

Lines changed: 109 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,6 @@ func UnmarshalManyPayload(in io.Reader, t reflect.Type) ([]interface{}, error) {
117117
return models, nil
118118
}
119119

120-
type unmarshal struct {
121-
attribute interface{}
122-
args []string
123-
fieldType reflect.StructField
124-
fieldValue reflect.Value
125-
}
126-
127120
func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node) (err error) {
128121

129122
defer func() {
@@ -147,7 +140,6 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node)
147140
fieldValue := modelValue.Field(i)
148141

149142
args := strings.Split(tag, ",")
150-
151143
if len(args) < 1 {
152144
er = ErrBadJSONAPIStructTag
153145
break
@@ -264,14 +256,7 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node)
264256
continue
265257
}
266258

267-
data := unmarshal{
268-
attribute,
269-
args,
270-
fieldType,
271-
fieldValue,
272-
}
273-
274-
value, err := unmarshalAttribute(data)
259+
value, err := unmarshalAttribute(attribute, args, fieldType.Type, fieldValue)
275260
if err != nil {
276261
er = err
277262
break
@@ -378,45 +363,63 @@ func assign(field, value reflect.Value) {
378363
}
379364
}
380365

381-
func unmarshalAttribute(data unmarshal) (value reflect.Value, err error) {
366+
func unmarshalAttribute(attribute interface{}, args []string, fieldType reflect.Type, fieldValue reflect.Value) (value reflect.Value, err error) {
382367

383-
value = reflect.ValueOf(data.attribute)
368+
value = reflect.ValueOf(attribute)
384369

385370
// Handle field of type []string
386-
if data.fieldValue.Type() == reflect.TypeOf([]string{}) {
387-
value, err = handleStringSlice(data)
371+
if fieldValue.Type() == reflect.TypeOf([]string{}) {
372+
value, err = handleStringSlice(attribute, args, fieldType, fieldValue)
388373
return
389374
}
390375

391376
// Handle field of type time.Time
392-
if data.fieldValue.Type() == reflect.TypeOf(time.Time{}) || data.fieldValue.Type() == reflect.TypeOf(new(time.Time)) {
393-
value, err = handleTime(data)
377+
if fieldValue.Type() == reflect.TypeOf(time.Time{}) || fieldValue.Type() == reflect.TypeOf(new(time.Time)) {
378+
value, err = handleTime(attribute, args, fieldType, fieldValue)
379+
return
380+
}
381+
382+
// Handle field of type struct
383+
if fieldValue.Type().Kind() == reflect.Struct {
384+
value, err = handleStruct(attribute, args, fieldType, fieldValue)
385+
return
386+
}
387+
388+
// Handle field of type struct
389+
if fieldValue.Type().Kind() == reflect.Struct {
390+
value, err = handleStruct(attribute, args, fieldType, fieldValue)
391+
return
392+
}
393+
394+
// Handle field containing slice of structs
395+
if fieldValue.Type().Kind() == reflect.Slice && reflect.TypeOf(fieldValue.Interface()).Elem().Kind() == reflect.Struct {
396+
value, err = handleStructSlice(attribute, args, fieldType, fieldValue)
394397
return
395398
}
396399

397400
// JSON value was a float (numeric)
398401
if value.Kind() == reflect.Float64 {
399-
value, err = handleNumeric(data)
402+
value, err = handleNumeric(attribute, args, fieldType, fieldValue)
400403
return
401404
}
402405

403406
// Field was a Pointer type
404-
if data.fieldValue.Kind() == reflect.Ptr {
405-
value, err = handlePointer(data)
407+
if fieldValue.Kind() == reflect.Ptr {
408+
value, err = handlePointer(attribute, args, fieldType, fieldValue)
406409
return
407410
}
408411

409412
// As a final catch-all, ensure types line up to avoid a runtime panic.
410-
if data.fieldValue.Kind() != value.Kind() {
413+
if fieldValue.Kind() != value.Kind() {
411414
err = ErrInvalidType
412415
return
413416
}
414417

415418
return
416419
}
417420

418-
func handleStringSlice(data unmarshal) (reflect.Value, error) {
419-
v := reflect.ValueOf(data.attribute)
421+
func handleStringSlice(attribute interface{}, args []string, fieldType reflect.Type, fieldValue reflect.Value) (reflect.Value, error) {
422+
v := reflect.ValueOf(attribute)
420423
values := make([]string, v.Len())
421424
for i := 0; i < v.Len(); i++ {
422425
values[i] = v.Index(i).Interface().(string)
@@ -425,13 +428,13 @@ func handleStringSlice(data unmarshal) (reflect.Value, error) {
425428
return reflect.ValueOf(values), nil
426429
}
427430

428-
func handleTime(data unmarshal) (reflect.Value, error) {
431+
func handleTime(attribute interface{}, args []string, fieldType reflect.Type, fieldValue reflect.Value) (reflect.Value, error) {
429432

430433
var isIso8601 bool
431-
v := reflect.ValueOf(data.attribute)
434+
v := reflect.ValueOf(attribute)
432435

433-
if len(data.args) > 2 {
434-
for _, arg := range data.args[2:] {
436+
if len(args) > 2 {
437+
for _, arg := range args[2:] {
435438
if arg == annotationISO8601 {
436439
isIso8601 = true
437440
}
@@ -451,7 +454,7 @@ func handleTime(data unmarshal) (reflect.Value, error) {
451454
return reflect.ValueOf(time.Now()), ErrInvalidISO8601
452455
}
453456

454-
if data.fieldValue.Kind() == reflect.Ptr {
457+
if fieldValue.Kind() == reflect.Ptr {
455458
return reflect.ValueOf(&t), nil
456459
}
457460

@@ -473,15 +476,15 @@ func handleTime(data unmarshal) (reflect.Value, error) {
473476
return reflect.ValueOf(t), nil
474477
}
475478

476-
func handleNumeric(data unmarshal) (reflect.Value, error) {
477-
v := reflect.ValueOf(data.attribute)
479+
func handleNumeric(attribute interface{}, args []string, fieldType reflect.Type, fieldValue reflect.Value) (reflect.Value, error) {
480+
v := reflect.ValueOf(attribute)
478481
floatValue := v.Interface().(float64)
479482

480483
var kind reflect.Kind
481-
if data.fieldValue.Kind() == reflect.Ptr {
482-
kind = data.fieldType.Type.Elem().Kind()
484+
if fieldValue.Kind() == reflect.Ptr {
485+
kind = fieldType.Elem().Kind()
483486
} else {
484-
kind = data.fieldType.Type.Kind()
487+
kind = fieldType.Kind()
485488
}
486489

487490
var numericValue reflect.Value
@@ -530,11 +533,11 @@ func handleNumeric(data unmarshal) (reflect.Value, error) {
530533
return numericValue, nil
531534
}
532535

533-
func handlePointer(data unmarshal) (reflect.Value, error) {
534-
t := data.fieldValue.Type()
536+
func handlePointer(attribute interface{}, args []string, fieldType reflect.Type, fieldValue reflect.Value) (reflect.Value, error) {
537+
t := fieldValue.Type()
535538
var concreteVal reflect.Value
536539

537-
switch cVal := data.attribute.(type) {
540+
switch cVal := attribute.(type) {
538541
case string:
539542
concreteVal = reflect.ValueOf(&cVal)
540543
case bool:
@@ -555,3 +558,68 @@ func handlePointer(data unmarshal) (reflect.Value, error) {
555558

556559
return concreteVal, nil
557560
}
561+
562+
func handleStruct(attribute interface{}, args []string, fieldType reflect.Type, fieldValue reflect.Value) (reflect.Value, error) {
563+
model := reflect.New(fieldValue.Type())
564+
565+
modelValue := model.Elem()
566+
modelType := model.Type().Elem()
567+
568+
var er error
569+
570+
for i := 0; i < modelValue.NumField(); i++ {
571+
fieldType := modelType.Field(i)
572+
tag := fieldType.Tag.Get("jsonapi")
573+
if tag == "" {
574+
continue
575+
}
576+
577+
fieldValue := modelValue.Field(i)
578+
579+
args := strings.Split(tag, ",")
580+
581+
if len(args) < 1 {
582+
er = ErrBadJSONAPIStructTag
583+
break
584+
}
585+
586+
if reflect.TypeOf(attribute).Kind() != reflect.Map {
587+
return model, nil
588+
}
589+
590+
attributes := reflect.ValueOf(attribute).Interface().(map[string]interface{})
591+
attribute := attributes[args[1]]
592+
593+
if attribute == nil {
594+
continue
595+
}
596+
597+
value, err := unmarshalAttribute(attribute, args, fieldType.Type, fieldValue)
598+
if err != nil {
599+
return model, nil
600+
}
601+
602+
assign(fieldValue, value)
603+
}
604+
605+
return model, er
606+
}
607+
608+
func handleStructSlice(attribute interface{}, args []string, fieldType reflect.Type, fieldValue reflect.Value) (reflect.Value, error) {
609+
models := reflect.New(fieldValue.Type()).Elem()
610+
dataMap := reflect.ValueOf(attribute).Interface().([]interface{})
611+
for _, data := range dataMap {
612+
model := reflect.New(fieldValue.Type().Elem()).Elem()
613+
modelType := model.Type()
614+
615+
value, err := handleStruct(data, []string{}, modelType, model)
616+
617+
if err != nil {
618+
continue
619+
}
620+
621+
models = reflect.Append(models, reflect.Indirect(value))
622+
}
623+
624+
return models, nil
625+
}

request_test.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -945,3 +945,109 @@ func sampleSerializedEmbeddedTestModel() *Blog {
945945

946946
return blog
947947
}
948+
949+
func TestUnmarshalNestedStruct(t *testing.T) {
950+
951+
boss := map[string]interface{}{
952+
"firstname": "Hubert",
953+
"surname": "Farnsworth",
954+
"age": 176,
955+
"hired-at": "2016-08-17T08:27:12Z",
956+
}
957+
958+
sample := map[string]interface{}{
959+
"data": map[string]interface{}{
960+
"type": "companies",
961+
"id": "123",
962+
"attributes": map[string]interface{}{
963+
"name": "Planet Express",
964+
"boss": boss,
965+
"founded-at": "2016-08-17T08:27:12Z",
966+
},
967+
},
968+
}
969+
970+
data, err := json.Marshal(sample)
971+
if err != nil {
972+
t.Fatal(err)
973+
}
974+
in := bytes.NewReader(data)
975+
out := new(Company)
976+
977+
if err := UnmarshalPayload(in, out); err != nil {
978+
t.Fatal(err)
979+
}
980+
981+
if out.Boss.Firstname != "Hubert" {
982+
t.Fatalf("Nested struct was not unmarshalled")
983+
}
984+
985+
if out.Boss.Age != 176 {
986+
t.Fatalf("Nested struct was not unmarshalled")
987+
}
988+
989+
if out.Boss.HiredAt.IsZero() {
990+
t.Fatalf("Nested struct was not unmarshalled")
991+
}
992+
}
993+
994+
func TestUnmarshalNestedStructSlice(t *testing.T) {
995+
996+
fry := map[string]interface{}{
997+
"firstname": "Philip J.",
998+
"surname": "Fry",
999+
"age": 25,
1000+
"hired-at": "2016-08-17T08:27:12Z",
1001+
}
1002+
1003+
bender := map[string]interface{}{
1004+
"firstname": "Bender Bending",
1005+
"surname": "Rodriguez",
1006+
"age": 19,
1007+
"hired-at": "2016-08-17T08:27:12Z",
1008+
}
1009+
1010+
deliveryCrew := map[string]interface{}{
1011+
"name": "Delivery Crew",
1012+
"members": []interface{}{
1013+
fry,
1014+
bender,
1015+
},
1016+
}
1017+
1018+
sample := map[string]interface{}{
1019+
"data": map[string]interface{}{
1020+
"type": "companies",
1021+
"id": "123",
1022+
"attributes": map[string]interface{}{
1023+
"name": "Planet Express",
1024+
"teams": []interface{}{
1025+
deliveryCrew,
1026+
},
1027+
},
1028+
},
1029+
}
1030+
1031+
data, err := json.Marshal(sample)
1032+
if err != nil {
1033+
t.Fatal(err)
1034+
}
1035+
in := bytes.NewReader(data)
1036+
out := new(Company)
1037+
1038+
if err := UnmarshalPayload(in, out); err != nil {
1039+
t.Fatal(err)
1040+
}
1041+
1042+
if out.Teams[0].Name != "Delivery Crew" {
1043+
t.Fatalf("Nested struct Team was not unmarshalled")
1044+
}
1045+
1046+
if len(out.Teams[0].Members) != 2 {
1047+
t.Fatalf("Nested struct Members were not unmarshalled")
1048+
}
1049+
1050+
if out.Teams[0].Members[0].Firstname != "Philip J." {
1051+
t.Fatalf("Nested struct member was not unmarshalled")
1052+
}
1053+
}

0 commit comments

Comments
 (0)