Skip to content
This repository was archived by the owner on Sep 4, 2025. It is now read-only.

Commit ccac636

Browse files
CrushedPixelshwoodard
authored andcommitted
Add support for attributes of custom defined types
1 parent 5307399 commit ccac636

3 files changed

Lines changed: 96 additions & 6 deletions

File tree

models_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,18 @@ type Employee struct {
176176
Age int `jsonapi:"attr,age"`
177177
HiredAt *time.Time `jsonapi:"attr,hired-at,iso8601"`
178178
}
179+
180+
type CustomIntType int
181+
type CustomFloatType float64
182+
type CustomStringType string
183+
184+
type CustomAttributeTypes struct {
185+
ID string `jsonapi:"primary,customtypes"`
186+
187+
Int CustomIntType `jsonapi:"attr,int"`
188+
IntPtr *CustomIntType `jsonapi:"attr,intptr"`
189+
IntPtrNull *CustomIntType `jsonapi:"attr,intptrnull"`
190+
191+
Float CustomFloatType `jsonapi:"attr,float"`
192+
String CustomStringType `jsonapi:"attr,string"`
193+
}

request.go

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -253,9 +253,11 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node)
253253
break
254254
}
255255

256-
assign(fieldValue, value)
257-
continue
258-
256+
// As a final catch-all, ensure types line up to avoid a runtime panic.
257+
if fieldValue.Kind() != v.Kind() {
258+
return ErrInvalidType
259+
}
260+
assignValue(fieldValue, reflect.ValueOf(val))
259261
} else if annotation == annotationRelation {
260262
isSlice := fieldValue.Type().Kind() == reflect.Slice
261263

@@ -347,10 +349,36 @@ func fullNode(n *Node, included *map[string]*Node) *Node {
347349
// assign will take the value specified and assign it to the field; if
348350
// field is expecting a ptr assign will assign a ptr.
349351
func assign(field, value reflect.Value) {
352+
value = reflect.Indirect(value)
353+
350354
if field.Kind() == reflect.Ptr {
351-
field.Set(value)
355+
// initialize pointer so it's value
356+
// can be set by assignValue
357+
field.Set(reflect.New(field.Type().Elem()))
358+
assignValue(field.Elem(), value)
352359
} else {
353-
field.Set(reflect.Indirect(value))
360+
assignValue(field, value)
361+
}
362+
}
363+
364+
// assign assigns the specified value to the field,
365+
// expecting both values not to be pointer types.
366+
func assignValue(field, value reflect.Value) {
367+
switch field.Kind() {
368+
case reflect.Int, reflect.Int8, reflect.Int16,
369+
reflect.Int32, reflect.Int64:
370+
field.SetInt(value.Int())
371+
case reflect.Uint, reflect.Uint8, reflect.Uint16,
372+
reflect.Uint32, reflect.Uint64, reflect.Uintptr:
373+
field.SetUint(value.Uint())
374+
case reflect.Float32, reflect.Float64:
375+
field.SetFloat(value.Float())
376+
case reflect.String:
377+
field.SetString(value.String())
378+
case reflect.Bool:
379+
field.SetBool(value.Bool())
380+
default:
381+
field.Set(value)
354382
}
355383
}
356384

@@ -588,7 +616,6 @@ func handleStruct(
588616
return reflect.Value{}, err
589617
}
590618

591-
592619
return model, nil
593620
}
594621

request_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,54 @@ func TestManyPayload_withLinks(t *testing.T) {
768768
}
769769
}
770770

771+
func TestUnmarshalCustomTypeAttributes(t *testing.T) {
772+
customInt := CustomIntType(5)
773+
customFloat := CustomFloatType(1.5)
774+
customString := CustomStringType("Test")
775+
776+
data := map[string]interface{}{
777+
"data": map[string]interface{}{
778+
"type": "customtypes",
779+
"id": "1",
780+
"attributes": map[string]interface{}{
781+
"int": customInt,
782+
"intptr": &customInt,
783+
"intptrnull": nil,
784+
785+
"float": customFloat,
786+
"string": customString,
787+
},
788+
},
789+
}
790+
payload, err := payload(data)
791+
if err != nil {
792+
t.Fatal(err)
793+
}
794+
795+
// Parse JSON API payload
796+
customAttributeTypes := new(CustomAttributeTypes)
797+
if err := UnmarshalPayload(bytes.NewReader(payload), customAttributeTypes); err != nil {
798+
t.Fatal(err)
799+
}
800+
801+
if expected, actual := customInt, customAttributeTypes.Int; expected != actual {
802+
t.Fatalf("Was expecting custom int to be `%s`, got `%s`", expected, actual)
803+
}
804+
if expected, actual := customInt, *customAttributeTypes.IntPtr; expected != actual {
805+
t.Fatalf("Was expecting custom int pointer to be `%s`, got `%s`", expected, actual)
806+
}
807+
if customAttributeTypes.IntPtrNull != nil {
808+
t.Fatalf("Was expecting custom int pointer to be <nil>, got `%s`", customAttributeTypes.IntPtrNull)
809+
}
810+
811+
if expected, actual := customFloat, customAttributeTypes.Float; expected != actual {
812+
t.Fatalf("Was expecting custom float to be `%s`, got `%s`", expected, actual)
813+
}
814+
if expected, actual := customString, customAttributeTypes.String; expected != actual {
815+
t.Fatalf("Was expecting custom string to be `%s`, got `%s`", expected, actual)
816+
}
817+
}
818+
771819
func samplePayloadWithoutIncluded() map[string]interface{} {
772820
return map[string]interface{}{
773821
"data": map[string]interface{}{

0 commit comments

Comments
 (0)