Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 05cf67e

Browse files
Alan Colonalancnet
Alan Colon
authored andcommittedAug 24, 2023
Further flush out ability to auto-bind to functions and types, including recursion.
Fix code coverage, support additional functions. Add bind examples Avoid errors where value IsZero Add type tag, nested resolvers on Bind Extend type Don't call .Type() on zero values
1 parent 4ebf270 commit 05cf67e

File tree

6 files changed

+728
-27
lines changed

6 files changed

+728
-27
lines changed
 

‎bind.go

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
package graphql
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"reflect"
8+
)
9+
10+
var ctxType = reflect.TypeOf((*context.Context)(nil)).Elem()
11+
var errType = reflect.TypeOf((*error)(nil)).Elem()
12+
13+
/*
14+
Bind will create a Field around a function formatted a certain way, or any value.
15+
16+
The input parameters can be, in any order,
17+
- context.Context, or *context.Context (optional)
18+
- An input struct, or pointer (optional)
19+
20+
The output parameters can be, in any order,
21+
- A primitive, an output struct, or pointer (required for use in schema)
22+
- error (optional)
23+
24+
Input or output types provided will be automatically bound using BindType.
25+
*/
26+
func Bind(bindTo interface{}, additionalFields ...Fields) *Field {
27+
combinedAdditionalFields := MergeFields(additionalFields...)
28+
val := reflect.ValueOf(bindTo)
29+
tipe := reflect.TypeOf(bindTo)
30+
if tipe.Kind() == reflect.Func {
31+
in := tipe.NumIn()
32+
out := tipe.NumOut()
33+
34+
var ctxIn *int
35+
var inputIn *int
36+
37+
var errOut *int
38+
var outputOut *int
39+
40+
queryArgs := FieldConfigArgument{}
41+
42+
if in > 2 {
43+
panic(fmt.Sprintf("Mismatch on number of inputs. Expected 0, 1, or 2. got %d.", tipe.NumIn()))
44+
}
45+
46+
if out > 2 {
47+
panic(fmt.Sprintf("Mismatch on number of outputs. Expected 0, 1, or 2, got %d.", tipe.NumOut()))
48+
}
49+
50+
// inTypes := make([]reflect.Type, in)
51+
// outTypes := make([]reflect.Type, out)
52+
53+
for i := 0; i < in; i++ {
54+
t := tipe.In(i)
55+
if t.Kind() == reflect.Ptr {
56+
t = t.Elem()
57+
}
58+
switch t {
59+
case ctxType:
60+
if ctxIn != nil {
61+
panic(fmt.Sprintf("Unexpected multiple *context.Context inputs."))
62+
}
63+
ctxIn = intP(i)
64+
default:
65+
if inputIn != nil {
66+
panic(fmt.Sprintf("Unexpected multiple inputs."))
67+
}
68+
inputType := tipe.In(i)
69+
if inputType.Kind() == reflect.Ptr {
70+
inputType = inputType.Elem()
71+
}
72+
inputFields := BindFields(reflect.New(inputType).Interface())
73+
for key, inputField := range inputFields {
74+
queryArgs[key] = &ArgumentConfig{
75+
Type: inputField.Type,
76+
}
77+
}
78+
79+
inputIn = intP(i)
80+
}
81+
}
82+
83+
for i := 0; i < out; i++ {
84+
t := tipe.Out(i)
85+
switch t.String() {
86+
case errType.String():
87+
if errOut != nil {
88+
panic(fmt.Sprintf("Unexpected multiple error outputs"))
89+
}
90+
errOut = intP(i)
91+
default:
92+
if outputOut != nil {
93+
panic(fmt.Sprintf("Unexpected multiple outputs"))
94+
}
95+
outputOut = intP(i)
96+
}
97+
}
98+
99+
resolve := func(p ResolveParams) (output interface{}, err error) {
100+
inputs := make([]reflect.Value, in)
101+
if ctxIn != nil {
102+
isPtr := tipe.In(*ctxIn).Kind() == reflect.Ptr
103+
if isPtr {
104+
if p.Context == nil {
105+
inputs[*ctxIn] = reflect.New(ctxType)
106+
} else {
107+
inputs[*ctxIn] = reflect.ValueOf(&p.Context)
108+
}
109+
} else {
110+
if p.Context == nil {
111+
inputs[*ctxIn] = reflect.New(ctxType).Elem()
112+
} else {
113+
inputs[*ctxIn] = reflect.ValueOf(p.Context).Convert(ctxType).Elem()
114+
}
115+
}
116+
}
117+
if inputIn != nil {
118+
var inputType, inputBaseType, sourceType, sourceBaseType reflect.Type
119+
sourceVal := reflect.ValueOf(p.Source)
120+
sourceExists := !sourceVal.IsZero()
121+
if sourceExists {
122+
sourceType = sourceVal.Type()
123+
if sourceType.Kind() == reflect.Ptr {
124+
sourceBaseType = sourceType.Elem()
125+
} else {
126+
sourceBaseType = sourceType
127+
}
128+
}
129+
inputType = tipe.In(*inputIn)
130+
isPtr := tipe.In(*inputIn).Kind() == reflect.Ptr
131+
if isPtr {
132+
inputBaseType = inputType.Elem()
133+
} else {
134+
inputBaseType = inputType
135+
}
136+
var input interface{}
137+
if sourceExists && sourceBaseType.AssignableTo(inputBaseType) {
138+
input = sourceVal.Interface()
139+
} else {
140+
input = reflect.New(inputBaseType).Interface()
141+
j, err := json.Marshal(p.Args)
142+
if err == nil {
143+
err = json.Unmarshal(j, &input)
144+
}
145+
if err != nil {
146+
return nil, err
147+
}
148+
}
149+
150+
inputs[*inputIn], err = convertValue(reflect.ValueOf(input), inputType)
151+
if err != nil {
152+
return nil, err
153+
}
154+
}
155+
results := val.Call(inputs)
156+
if errOut != nil {
157+
val := results[*errOut].Interface()
158+
if val != nil {
159+
err = val.(error)
160+
}
161+
if err != nil {
162+
return output, err
163+
}
164+
}
165+
if outputOut != nil {
166+
var val reflect.Value
167+
val, err = convertValue(results[*outputOut], tipe.Out(*outputOut))
168+
if err != nil {
169+
return nil, err
170+
}
171+
if !val.IsZero() {
172+
output = val.Interface()
173+
}
174+
}
175+
return output, err
176+
}
177+
178+
var outputType Output
179+
if outputOut != nil {
180+
outputType = BindType(tipe.Out(*outputOut))
181+
extendType(outputType, combinedAdditionalFields)
182+
}
183+
184+
field := &Field{
185+
Type: outputType,
186+
Resolve: resolve,
187+
Args: queryArgs,
188+
}
189+
190+
return field
191+
} else if tipe.Kind() == reflect.Struct {
192+
fieldType := BindType(reflect.TypeOf(bindTo))
193+
extendType(fieldType, combinedAdditionalFields)
194+
field := &Field{
195+
Type: fieldType,
196+
Resolve: func(p ResolveParams) (data interface{}, err error) {
197+
return bindTo, nil
198+
},
199+
}
200+
return field
201+
} else {
202+
if len(additionalFields) > 0 {
203+
panic("Cannot add field resolvers to a scalar type.")
204+
}
205+
return &Field{
206+
Type: getGraphType(tipe),
207+
Resolve: func(p ResolveParams) (data interface{}, err error) {
208+
return bindTo, nil
209+
},
210+
}
211+
}
212+
}
213+
214+
func extendType(t Type, fields Fields) {
215+
switch t.(type) {
216+
case *Object:
217+
object := t.(*Object)
218+
for fieldName, fieldConfig := range fields {
219+
object.AddFieldConfig(fieldName, fieldConfig)
220+
}
221+
return
222+
case *List:
223+
list := t.(*List)
224+
extendType(list.OfType, fields)
225+
return
226+
}
227+
}
228+
229+
func convertValue(value reflect.Value, targetType reflect.Type) (ret reflect.Value, err error) {
230+
if !value.IsValid() || value.IsZero() {
231+
return reflect.Zero(targetType), nil
232+
}
233+
if value.Type().Kind() == reflect.Ptr {
234+
if targetType.Kind() == reflect.Ptr {
235+
return value, nil
236+
} else {
237+
return value.Elem(), nil
238+
}
239+
} else {
240+
if targetType.Kind() == reflect.Ptr {
241+
// Will throw an informative error
242+
return value.Convert(targetType), nil
243+
} else {
244+
return value, nil
245+
}
246+
}
247+
}
248+
249+
func intP(i int) *int {
250+
return &i
251+
}

‎bind_test.go

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
package graphql_test
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"errors"
7+
"fmt"
8+
"log"
9+
"strings"
10+
"testing"
11+
"time"
12+
13+
"github.com/graphql-go/graphql"
14+
)
15+
16+
type HelloOutput struct {
17+
Message string `json:"message"`
18+
}
19+
20+
func Hello(ctx *context.Context) (output *HelloOutput, err error) {
21+
output = &HelloOutput{
22+
Message: "Hello World",
23+
}
24+
return output, nil
25+
}
26+
27+
func Hellos() []HelloOutput {
28+
return []HelloOutput{
29+
{
30+
Message: "Hello One",
31+
},
32+
{
33+
Message: "Hello Two",
34+
},
35+
}
36+
}
37+
38+
func Upper(ctx *context.Context, source HelloOutput) string {
39+
return strings.ToUpper(source.Message)
40+
}
41+
42+
type GreetingInput struct {
43+
Name string `json:"name"`
44+
}
45+
46+
type GreetingOutput struct {
47+
Message string `json:"message"`
48+
Timestamp time.Time `json:"timestamp"`
49+
}
50+
51+
func GreetingPtr(ctx *context.Context, input *GreetingInput) (output *GreetingOutput, err error) {
52+
return &GreetingOutput{
53+
Message: fmt.Sprintf("Hello %s.", input.Name),
54+
Timestamp: time.Now(),
55+
}, nil
56+
}
57+
58+
func Greeting(ctx context.Context, input GreetingInput) (output GreetingOutput, err error) {
59+
return GreetingOutput{
60+
Message: fmt.Sprintf("Hello %s.", input.Name),
61+
Timestamp: time.Now(),
62+
}, nil
63+
}
64+
65+
type FriendRecur struct {
66+
Name string `json:"name"`
67+
Friends []FriendRecur `json:"friends"`
68+
}
69+
70+
func friends(ctx *context.Context) (output *FriendRecur) {
71+
recursiveFriendRecur := FriendRecur{
72+
Name: "Recursion",
73+
}
74+
recursiveFriendRecur.Friends = make([]FriendRecur, 2)
75+
recursiveFriendRecur.Friends[0] = recursiveFriendRecur
76+
recursiveFriendRecur.Friends[1] = recursiveFriendRecur
77+
78+
return &FriendRecur{
79+
Name: "Alan",
80+
Friends: []FriendRecur{
81+
recursiveFriendRecur,
82+
{
83+
Name: "Samantha",
84+
Friends: []FriendRecur{
85+
{
86+
Name: "Olivia",
87+
},
88+
{
89+
Name: "Eric",
90+
},
91+
},
92+
},
93+
{
94+
Name: "Brian",
95+
Friends: []FriendRecur{
96+
{
97+
Name: "Windy",
98+
},
99+
{
100+
Name: "Kevin",
101+
},
102+
},
103+
},
104+
{
105+
Name: "Kevin",
106+
Friends: []FriendRecur{
107+
{
108+
Name: "Sergei",
109+
},
110+
{
111+
Name: "Michael",
112+
},
113+
},
114+
},
115+
},
116+
}
117+
}
118+
119+
func TestBindHappyPath(t *testing.T) {
120+
// Schema
121+
fields := graphql.Fields{
122+
"hello": graphql.Bind(Hello),
123+
"hellos": graphql.Bind(Hellos, graphql.Fields{
124+
"upper": graphql.Bind(Upper),
125+
}),
126+
"greeting": graphql.Bind(Greeting),
127+
"greetingPtr": graphql.Bind(GreetingPtr),
128+
"friends": graphql.Bind(friends),
129+
"string": graphql.Bind("Hello World"),
130+
"number": graphql.Bind(12345),
131+
"float": graphql.Bind(123.45),
132+
"anonymous": graphql.Bind(struct {
133+
SomeField string `json:"someField"`
134+
}{
135+
SomeField: "Some Value",
136+
}),
137+
"simpleFunc": graphql.Bind(func() string {
138+
return "Hello World"
139+
}),
140+
}
141+
rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: fields}
142+
schemaConfig := graphql.SchemaConfig{Query: graphql.NewObject(rootQuery)}
143+
schema, err := graphql.NewSchema(schemaConfig)
144+
if err != nil {
145+
log.Fatalf("failed to create new schema, error: %v", err)
146+
}
147+
148+
// Query
149+
query := `
150+
{
151+
hello {
152+
message
153+
upper
154+
}
155+
hellos {
156+
message
157+
upper
158+
}
159+
greeting(name:"Alan") {
160+
message
161+
timestamp
162+
}
163+
greetingPtr(name:"Alan") {
164+
message
165+
timestamp
166+
}
167+
friends {
168+
name
169+
friends {
170+
name
171+
friends {
172+
name
173+
friends {
174+
name
175+
friends {
176+
name
177+
}
178+
}
179+
}
180+
}
181+
}
182+
string
183+
number
184+
float
185+
anonymous {
186+
someField
187+
}
188+
simpleFunc
189+
}
190+
`
191+
params := graphql.Params{Schema: schema, RequestString: query}
192+
r := graphql.Do(params)
193+
if len(r.Errors) > 0 {
194+
t.Errorf("failed to execute graphql operation, errors: %+v", r.Errors)
195+
}
196+
json, err := json.MarshalIndent(r.Data, "", " ")
197+
fmt.Println(string(json))
198+
}
199+
200+
func TestBindPanicImproperInput(t *testing.T) {
201+
defer func() {
202+
if r := recover(); r == nil {
203+
t.Error("Expected Bind to panic due to improper function signature")
204+
}
205+
}()
206+
graphql.Bind(func(a, b, c string) {})
207+
}
208+
209+
func TestBindPanicImproperOutput(t *testing.T) {
210+
defer func() {
211+
if r := recover(); r == nil {
212+
t.Error("Expected Bind to panic due to improper function signature")
213+
}
214+
}()
215+
graphql.Bind(func() (string, string) { return "Hello", "World" })
216+
}
217+
218+
func TestBindWithRuntimeError(t *testing.T) {
219+
rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: graphql.Fields{
220+
"throwError": graphql.Bind(func() (string, error) {
221+
return "", errors.New("Some Error")
222+
}),
223+
}}
224+
schemaConfig := graphql.SchemaConfig{Query: graphql.NewObject(rootQuery)}
225+
schema, err := graphql.NewSchema(schemaConfig)
226+
if err != nil {
227+
log.Fatalf("failed to create new schema, error: %v", err)
228+
}
229+
230+
// Query
231+
query := `
232+
{
233+
throwError
234+
}
235+
`
236+
params := graphql.Params{Schema: schema, RequestString: query}
237+
r := graphql.Do(params)
238+
if len(r.Errors) == 0 {
239+
t.Error("Expected error")
240+
}
241+
}

‎examples/bind-complex/main.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"errors"
7+
"fmt"
8+
"log"
9+
10+
"github.com/graphql-go/graphql"
11+
)
12+
13+
var people = []Person{
14+
{
15+
Name: "Alan",
16+
Friends: []Person{
17+
{
18+
Name: "Nadeem",
19+
Friends: []Person{
20+
{
21+
Name: "Heidi",
22+
},
23+
},
24+
},
25+
},
26+
},
27+
}
28+
29+
type Person struct {
30+
Name string `json:"name"`
31+
Friends []Person `json:"friends"`
32+
}
33+
34+
type GetPersonInput struct {
35+
Name string `json:"name"`
36+
}
37+
38+
type GetPersonOutput struct {
39+
Person
40+
}
41+
42+
func GetPerson(ctx context.Context, input GetPersonInput) (*GetPersonOutput, error) {
43+
for _, person := range people {
44+
if person.Name == input.Name {
45+
return &GetPersonOutput{
46+
Person: person,
47+
}, nil
48+
}
49+
}
50+
return nil, errors.New("Could not find person.")
51+
}
52+
53+
func main() {
54+
rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: graphql.Fields{
55+
"person": graphql.Bind(GetPerson),
56+
}}
57+
58+
schemaConfig := graphql.SchemaConfig{Query: graphql.NewObject(rootQuery)}
59+
schema, err := graphql.NewSchema(schemaConfig)
60+
if err != nil {
61+
log.Fatalf("failed to create new schema, error: %v", err)
62+
}
63+
64+
// Query
65+
query := `
66+
{
67+
person(name: "Alan") {
68+
name
69+
friends {
70+
name
71+
friends {
72+
name
73+
}
74+
}
75+
}
76+
}
77+
`
78+
params := graphql.Params{Schema: schema, RequestString: query}
79+
r := graphql.Do(params)
80+
if len(r.Errors) > 0 {
81+
log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors)
82+
}
83+
rJSON, _ := json.Marshal(r)
84+
fmt.Printf("%s \n", rJSON)
85+
}

‎examples/bind-simple/main.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"log"
7+
8+
"github.com/graphql-go/graphql"
9+
)
10+
11+
type GreetingInput struct {
12+
Name string `json:"name"`
13+
}
14+
15+
func Greeting(input GreetingInput) string {
16+
return fmt.Sprintf("Hello %s", input.Name)
17+
}
18+
19+
func main() {
20+
rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: graphql.Fields{
21+
"greeting": graphql.Bind(Greeting),
22+
}}
23+
24+
schemaConfig := graphql.SchemaConfig{Query: graphql.NewObject(rootQuery)}
25+
schema, err := graphql.NewSchema(schemaConfig)
26+
if err != nil {
27+
log.Fatalf("failed to create new schema, error: %v", err)
28+
}
29+
30+
// Query
31+
query := `
32+
{
33+
greeting(name: "Alan")
34+
}
35+
`
36+
params := graphql.Params{Schema: schema, RequestString: query}
37+
r := graphql.Do(params)
38+
if len(r.Errors) > 0 {
39+
log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors)
40+
}
41+
rJSON, _ := json.Marshal(r)
42+
fmt.Printf("%s \n", rJSON)
43+
}

‎executor.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -943,7 +943,7 @@ func DefaultResolveFn(p ResolveParams) (interface{}, error) {
943943
}
944944

945945
// try to resolve p.Source as a struct
946-
if sourceVal.IsValid() && sourceVal.Type().Kind() == reflect.Ptr {
946+
if sourceVal.IsValid() && !sourceVal.IsZero() && sourceVal.Type().Kind() == reflect.Ptr {
947947
sourceVal = sourceVal.Elem()
948948
}
949949
if !sourceVal.IsValid() {

‎util.go

Lines changed: 107 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,93 @@ import (
55
"fmt"
66
"reflect"
77
"strings"
8+
"time"
89
)
910

1011
const TAG = "json"
12+
const TYPETAG = "graphql"
13+
14+
var boundTypes = map[string]*Object{}
15+
var anonTypes = 0
16+
var timeType = reflect.TypeOf(time.Time{})
17+
18+
func MergeFields(fieldses ...Fields) (ret Fields) {
19+
ret = Fields{}
20+
for _, fields := range fieldses {
21+
for key, field := range fields {
22+
if _, ok := ret[key]; ok {
23+
panic(fmt.Sprintf("Dupliate field: %s", key))
24+
}
25+
ret[key] = field
26+
}
27+
}
28+
return ret
29+
}
30+
31+
func BindType(tipe reflect.Type) Type {
32+
if tipe.Kind() == reflect.Ptr {
33+
tipe = tipe.Elem()
34+
}
35+
36+
kind := tipe.Kind()
37+
switch kind {
38+
case reflect.String:
39+
return String
40+
case reflect.Int, reflect.Int8, reflect.Int32, reflect.Int64:
41+
return Int
42+
case reflect.Float32, reflect.Float64:
43+
return Float
44+
case reflect.Bool:
45+
return Boolean
46+
case reflect.Slice:
47+
return getGraphList(tipe)
48+
}
49+
50+
typeName := safeName(tipe)
51+
object, ok := boundTypes[typeName]
52+
if !ok {
53+
// Allows for recursion
54+
object = &Object{}
55+
boundTypes[typeName] = object
56+
*object = *NewObject(ObjectConfig{
57+
Name: typeName,
58+
Fields: BindFields(reflect.New(tipe).Interface()),
59+
})
60+
}
61+
62+
return object
63+
}
64+
65+
func safeName(tipe reflect.Type) string {
66+
name := fmt.Sprint(tipe)
67+
if strings.HasPrefix(name, "struct ") {
68+
anonTypes++
69+
name = fmt.Sprintf("Anon%d", anonTypes)
70+
} else {
71+
name = strings.Replace(fmt.Sprint(tipe), ".", "_", -1)
72+
}
73+
return name
74+
}
75+
76+
func getType(typeTag string) Output {
77+
switch strings.ToLower(typeTag) {
78+
case "int":
79+
return Int
80+
case "float":
81+
return Float
82+
case "string":
83+
return String
84+
case "boolean":
85+
return Boolean
86+
case "id":
87+
return ID
88+
case "datetime":
89+
return DateTime
90+
default:
91+
panic(fmt.Sprintf("Unsupported graphql type: %s", typeTag))
92+
}
93+
}
1194

12-
// can't take recursive slice type
13-
// e.g
14-
// type Person struct{
15-
// Friends []Person
16-
// }
17-
// it will throw panic stack-overflow
1895
func BindFields(obj interface{}) Fields {
1996
t := reflect.TypeOf(obj)
2097
v := reflect.ValueOf(obj)
@@ -33,14 +110,19 @@ func BindFields(obj interface{}) Fields {
33110
continue
34111
}
35112

113+
typeTag := field.Tag.Get(TYPETAG)
114+
36115
fieldType := field.Type
37116

38117
if fieldType.Kind() == reflect.Ptr {
39118
fieldType = fieldType.Elem()
40119
}
41-
42120
var graphType Output
43-
if fieldType.Kind() == reflect.Struct {
121+
if typeTag != "" {
122+
graphType = getType(typeTag)
123+
} else if fieldType == timeType {
124+
graphType = DateTime
125+
} else if fieldType.Kind() == reflect.Struct {
44126
itf := v.Field(i).Interface()
45127
if _, ok := itf.(encoding.TextMarshaler); ok {
46128
fieldType = reflect.TypeOf("")
@@ -53,10 +135,7 @@ func BindFields(obj interface{}) Fields {
53135
fields = appendFields(fields, structFields)
54136
continue
55137
} else {
56-
graphType = NewObject(ObjectConfig{
57-
Name: tag,
58-
Fields: structFields,
59-
})
138+
graphType = BindType(fieldType)
60139
}
61140
}
62141

@@ -110,11 +189,7 @@ func getGraphList(tipe reflect.Type) *List {
110189
}
111190
// finally bind object
112191
t := reflect.New(tipe.Elem())
113-
name := strings.Replace(fmt.Sprint(tipe.Elem()), ".", "_", -1)
114-
obj := NewObject(ObjectConfig{
115-
Name: name,
116-
Fields: BindFields(t.Elem().Interface()),
117-
})
192+
obj := BindType(t.Elem().Type())
118193
return NewList(obj)
119194
}
120195

@@ -132,21 +207,27 @@ func extractValue(originTag string, obj interface{}) interface{} {
132207
field := val.Type().Field(j)
133208
found := originTag == extractTag(field.Tag)
134209
if field.Type.Kind() == reflect.Struct {
135-
itf := val.Field(j).Interface()
210+
fieldVal := val.Field(j)
211+
if !fieldVal.IsZero() {
212+
itf := fieldVal.Interface()
136213

137-
if str, ok := itf.(encoding.TextMarshaler); ok && found {
138-
byt, _ := str.MarshalText()
139-
return string(byt)
140-
}
214+
if str, ok := itf.(encoding.TextMarshaler); ok && found {
215+
byt, _ := str.MarshalText()
216+
return string(byt)
217+
}
141218

142-
res := extractValue(originTag, itf)
143-
if res != nil {
144-
return res
219+
res := extractValue(originTag, itf)
220+
if res != nil {
221+
return res
222+
}
145223
}
146224
}
147225

148226
if found {
149-
return reflect.Indirect(val.Field(j)).Interface()
227+
fieldVal := val.Field(j)
228+
if !fieldVal.IsZero() {
229+
return reflect.Indirect(fieldVal).Interface()
230+
}
150231
}
151232
}
152233
return nil

0 commit comments

Comments
 (0)
Please sign in to comment.