Skip to content

Commit c37f5ba

Browse files
authored
Merge pull request #5 from zerodha/feat-custom-struct
feat: support unmarshalling custom structs with json.Unmarshaller
2 parents 367f02e + efc0988 commit c37f5ba

File tree

2 files changed

+79
-29
lines changed

2 files changed

+79
-29
lines changed

fastglue_test.go

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -680,25 +680,39 @@ func TestAnyHandler(t *testing.T) {
680680
}
681681
}
682682

683+
type customType struct {
684+
Foo string
685+
}
686+
687+
func (t *customType) UnmarshalJSON(b []byte) error {
688+
t.Foo = string(b)
689+
return nil
690+
}
691+
692+
var _ json.Unmarshaler = (*customType)(nil)
693+
683694
func TestScanArgs(t *testing.T) {
684695
type test struct {
685-
Str1 string `url:"str1"`
686-
StrBlock string `url:"-"`
687-
StrNoTag *string
688-
Strings []string `url:"str"`
689-
Bytes []byte `url:"bytes"`
690-
Int1 int `url:"int1"`
691-
Ints []int `url:"int"`
692-
NonExistentInts []int `url:"nonint"`
693-
Bool1 bool `url:"bool1"`
694-
Bools []bool `url:"bool"`
695-
NonExistent []string `url:"non"`
696-
BadNum int `url:"badnum"`
697-
BadNumSlice []int `url:"badnumslice"`
698-
OtherTag string `form:"otherval"`
699-
OmitEmpty string `form:"otherval,omitempty"`
700-
OtherTags string `url:"othertags" json:"othertags"`
701-
NaN float64 `url:"nan" json:"nan"`
696+
Str1 string `url:"str1"`
697+
StrBlock string `url:"-"`
698+
StrNoTag *string
699+
Strings []string `url:"str"`
700+
Bytes []byte `url:"bytes"`
701+
Int1 int `url:"int1"`
702+
Ints []int `url:"int"`
703+
NonExistentInts []int `url:"nonint"`
704+
Bool1 bool `url:"bool1"`
705+
Bools []bool `url:"bool"`
706+
NonExistent []string `url:"non"`
707+
BadNum int `url:"badnum"`
708+
BadNumSlice []int `url:"badnumslice"`
709+
OtherTag string `form:"otherval"`
710+
OmitEmpty string `form:"otherval,omitempty"`
711+
OtherTags string `url:"othertags" json:"othertags"`
712+
NaN float64 `url:"nan" json:"nan"`
713+
CustomStruct customType `url:"custom"`
714+
CustomPointer *customType `url:"custom_pointer"`
715+
CustomPointerNonExistent *customType `url:"custom_pointer_non_existent"`
702716
}
703717
var o test
704718

@@ -716,28 +730,33 @@ func TestScanArgs(t *testing.T) {
716730
args.Add("bool", "false")
717731
args.Add("bool", "f")
718732
args.Add("bool", "t")
733+
args.Add("custom", "bar")
734+
args.Add("custom_pointer", "bar")
719735

720736
_, err := ScanArgs(args, &o, "url")
721737
if err != nil {
722738
t.Fatalf("Got unexpected error: %v", err)
723739
}
724740

725741
exp := test{
726-
Str1: "string1",
727-
Strings: []string{"str1", "str2", "str3"},
728-
Bytes: []byte("manybytes"),
729-
Int1: 123,
730-
Ints: []int{456, 789},
731-
NonExistentInts: nil,
732-
Bool1: true,
733-
Bools: []bool{true, false, false, true},
734-
BadNum: 0,
735-
BadNumSlice: nil,
742+
Str1: "string1",
743+
Strings: []string{"str1", "str2", "str3"},
744+
Bytes: []byte("manybytes"),
745+
Int1: 123,
746+
Ints: []int{456, 789},
747+
NonExistentInts: nil,
748+
Bool1: true,
749+
Bools: []bool{true, false, false, true},
750+
BadNum: 0,
751+
BadNumSlice: nil,
752+
CustomStruct: customType{Foo: "bar"},
753+
CustomPointer: &customType{Foo: "bar"},
754+
CustomPointerNonExistent: nil,
736755
}
737756
if !reflect.DeepEqual(exp, o) {
738757
t.Error("scan structs don't match. expected != scanned")
739-
fmt.Printf("%#v", exp)
740-
fmt.Printf("%#v", o)
758+
fmt.Printf("expected:\n%#v\n", exp)
759+
fmt.Printf("got:\n%#v\n", o)
741760
}
742761

743762
args.Add("badnum", "abc")

utils.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package fastglue
22

33
import (
4+
"encoding/json"
45
"fmt"
56
"math"
67
"reflect"
@@ -125,6 +126,36 @@ func setVal(f reflect.Value, val string) (bool, error) {
125126
return false, fmt.Errorf("expected boolean")
126127
}
127128
f.SetBool(b)
129+
case reflect.Struct:
130+
typ := f.Type()
131+
receiver := reflect.New(typ).Interface()
132+
133+
if jR, ok := receiver.(json.Unmarshaler); ok {
134+
err := jR.UnmarshalJSON([]byte(val))
135+
if err != nil {
136+
return false, fmt.Errorf("failed to decode `%v`, got: `%s` (%v)", f.Type(), val, err)
137+
}
138+
f.Set(reflect.ValueOf(receiver).Elem())
139+
return true, nil
140+
}
141+
142+
return false, nil
143+
case reflect.Ptr:
144+
if f.Type().Elem().Kind() == reflect.Struct {
145+
typ := f.Type().Elem()
146+
receiver := reflect.New(typ).Interface()
147+
148+
if jR, ok := receiver.(json.Unmarshaler); ok {
149+
err := jR.UnmarshalJSON([]byte(val))
150+
if err != nil {
151+
return false, fmt.Errorf("failed to decode `%v`, got: `%s` (%v)", f.Type(), val, err)
152+
}
153+
f.Set(reflect.ValueOf(receiver))
154+
return true, nil
155+
}
156+
}
157+
158+
return false, nil
128159
default:
129160
return false, nil
130161
}

0 commit comments

Comments
 (0)