Skip to content

Commit 232b662

Browse files
committed
add feature nested pointer support(#21)
1 parent 38926db commit 232b662

File tree

4 files changed

+214
-16
lines changed

4 files changed

+214
-16
lines changed

README.md

+77
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,83 @@ fmt.Println(example.Bar) //Prints: 0
6666

6767
```
6868

69+
Pointer Set
70+
-------
71+
72+
Pointer field struct is a tricky usage to avoid covering existed values.
73+
74+
Take the basic example in the above section and change it slightly:
75+
```go
76+
77+
type ExamplePointer struct {
78+
Foo *bool `default:"true"` //<-- StructTag with a default key
79+
Bar *string `default:"example"`
80+
Qux *int `default:"22"`
81+
Oed *int64 `default:"64"`
82+
}
83+
84+
...
85+
86+
boolZero := false
87+
stringZero := ""
88+
intZero := 0
89+
example := &ExamplePointer{
90+
Foo: &boolZero,
91+
Bar: &stringZero,
92+
Qux: &intZero,
93+
}
94+
defaults.SetDefaults(example)
95+
96+
fmt.Println(*example.Foo) //Prints: false (zero value `false` for bool but not for bool ptr)
97+
fmt.Println(*example.Bar) //Prints: "" (print "" which set in advance, not "example" for default)
98+
fmt.Println(*example.Qux) //Prints: 0 (0 instead of 22)
99+
fmt.Println(*example.Oed) //Prints: 64 (64, because the ptr addr is nil when SetDefaults)
100+
101+
```
102+
103+
It's also a very useful feature for web application which default values are needed while binding request json.
104+
105+
For example:
106+
```go
107+
type ExamplePostBody struct {
108+
Foo *bool `json:"foo" default:"true"` //<-- StructTag with a default key
109+
Bar *string `json:"bar" default:"example"`
110+
Qux *int `json:"qux" default:"22"`
111+
Oed *int64 `json:"oed" default:"64"`
112+
}
113+
```
114+
115+
HTTP request seems like this:
116+
```bash
117+
curl --location --request POST ... \
118+
... \
119+
--header 'Content-Type: application/json' \
120+
--data-raw '{
121+
"foo": false,
122+
"bar": "",
123+
"qux": 0
124+
}'
125+
```
126+
127+
Request handler:
128+
```go
129+
func PostExampleHandler(c *gin.Context) {
130+
var reqBody ExamplePostBody
131+
if err := c.ShouldBindJSON(&reqBody); err != nil {
132+
c.JSON(http.StatusBadRequest, nil)
133+
return
134+
}
135+
defaults.SetDefaults(&reqBody)
136+
137+
fmt.Println(*reqBody.Foo) //Prints: false (zero value `false` for bool but not for bool ptr)
138+
fmt.Println(*reqBody.Bar) //Prints: "" (print "" which set in advance, not "example" for default)
139+
fmt.Println(*reqBody.Qux) //Prints: 0 (0 instead of 22, did not confused from whether zero value is in json or not)
140+
fmt.Println(*reqBody.Oed) //Prints: 64 (In this case "oed" is not in req json, so set default 64)
141+
142+
...
143+
}
144+
```
145+
69146
License
70147
-------
71148

defaults.go

+45-11
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,16 @@ import (
1212
// the StructTag with name "default" and the directed value.
1313
//
1414
// Usage
15-
// type ExampleBasic struct {
16-
// Foo bool `default:"true"`
17-
// Bar string `default:"33"`
18-
// Qux int8
19-
// Dur time.Duration `default:"2m3s"`
20-
// }
2115
//
22-
// foo := &ExampleBasic{}
23-
// SetDefaults(foo)
16+
// type ExampleBasic struct {
17+
// Foo bool `default:"true"`
18+
// Bar string `default:"33"`
19+
// Qux int8
20+
// Dur time.Duration `default:"2m3s"`
21+
// }
22+
//
23+
// foo := &ExampleBasic{}
24+
// SetDefaults(foo)
2425
func SetDefaults(variable interface{}) {
2526
getDefaultFiller().Fill(variable)
2627
}
@@ -90,7 +91,11 @@ func newDefaultFiller() *Filler {
9091
types := make(map[TypeHash]FillerFunc, 1)
9192
types["time.Duration"] = func(field *FieldData) {
9293
d, _ := time.ParseDuration(field.TagValue)
93-
field.Value.Set(reflect.ValueOf(d))
94+
if field.Value.Kind() == reflect.Ptr {
95+
field.Value.Set(reflect.ValueOf(&d))
96+
} else {
97+
field.Value.Set(reflect.ValueOf(d))
98+
}
9499
}
95100

96101
funcs[reflect.Slice] = func(field *FieldData) {
@@ -107,6 +112,16 @@ func newDefaultFiller() *Filler {
107112
fields := getDefaultFiller().GetFieldsFromValue(field.Value.Index(i), nil)
108113
getDefaultFiller().SetDefaultValues(fields)
109114
}
115+
case reflect.Ptr:
116+
count := field.Value.Len()
117+
for i := 0; i < count; i++ {
118+
if field.Value.Index(i).IsZero() {
119+
newValue := reflect.New(field.Value.Index(i).Type().Elem())
120+
field.Value.Index(i).Set(newValue)
121+
}
122+
fields := getDefaultFiller().GetFieldsFromValue(field.Value.Index(i).Elem(), nil)
123+
getDefaultFiller().SetDefaultValues(fields)
124+
}
110125
default:
111126
//处理形如 [1,2,3,4]
112127
reg := regexp.MustCompile(`^\[(.*)\]$`)
@@ -134,6 +149,27 @@ func newDefaultFiller() *Filler {
134149
}
135150
}
136151

152+
funcs[reflect.Ptr] = func(field *FieldData) {
153+
k := field.Value.Type().Elem().Kind()
154+
if k != reflect.Struct && k != reflect.Slice && k != reflect.Ptr && field.TagValue == "" {
155+
return
156+
}
157+
if field.Value.IsNil() {
158+
v := reflect.New(field.Value.Type().Elem())
159+
field.Value.Set(v)
160+
}
161+
elemField := &FieldData{
162+
Value: field.Value.Elem(),
163+
Field: reflect.StructField{
164+
Type: field.Field.Type.Elem(),
165+
Tag: field.Field.Tag,
166+
},
167+
TagValue: field.TagValue,
168+
Parent: nil,
169+
}
170+
funcs[field.Value.Elem().Kind()](elemField)
171+
}
172+
137173
return &Filler{FuncByKind: funcs, FuncByType: types, Tag: "default"}
138174
}
139175

@@ -159,13 +195,11 @@ func parseDateTimeString(data string) string {
159195
case "date":
160196
str := time.Now().AddDate(values[0], values[1], values[2]).Format("2006-01-02")
161197
data = strings.Replace(data, match[0], str, -1)
162-
break
163198
case "time":
164199
str := time.Now().Add((time.Duration(values[0]) * time.Hour) +
165200
(time.Duration(values[1]) * time.Minute) +
166201
(time.Duration(values[2]) * time.Second)).Format("15:04:05")
167202
data = strings.Replace(data, match[0], str, -1)
168-
break
169203
}
170204
}
171205
}

defaults_test.go

+77
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ type Child struct {
3232
Age int `default:"10"`
3333
}
3434

35+
type ChildPtr struct {
36+
Name *string
37+
Age *int `default:"10"`
38+
}
39+
3540
type ExampleBasic struct {
3641
Bool bool `default:"true"`
3742
Integer int `default:"33"`
@@ -61,6 +66,39 @@ type ExampleBasic struct {
6166
StringSliceSlice [][]string `default:"[[1],[]]"`
6267

6368
DateTime string `default:"{{date:1,-10,0}} {{time:1,-5,10}}"`
69+
70+
BoolPtr *bool `default:"false"`
71+
IntPtr *int `default:"33"`
72+
Int8Ptr *int8 `default:"8"`
73+
Int16Ptr *int16 `default:"16"`
74+
Int32Ptr *int32 `default:"32"`
75+
Int64Ptr *int64 `default:"64"`
76+
UIntPtr *uint `default:"11"`
77+
UInt8Ptr *uint8 `default:"18"`
78+
UInt16Ptr *uint16 `default:"116"`
79+
UInt32Ptr *uint32 `default:"132"`
80+
UInt64Ptr *uint64 `default:"164"`
81+
Float32Ptr *float32 `default:"3.2"`
82+
Float64Ptr *float64 `default:"6.4"`
83+
DurationPtr *time.Duration `default:"1s"`
84+
SecondPtr *time.Duration `default:"1s"`
85+
StructPtr *struct {
86+
Bool bool `default:"true"`
87+
Integer *int `default:"33"`
88+
}
89+
PtrStructPtr **struct {
90+
Bool bool `default:"false"`
91+
Integer *int `default:"33"`
92+
}
93+
ChildrenPtr []*ChildPtr
94+
PtrChildrenPtr *[]*ChildPtr
95+
PtrPtrChildrenPtr **[]*ChildPtr
96+
PtrStringSliceNoTag *[]string
97+
PtrStringSlice *[]string `default:"[1,2,3,4]"`
98+
PtrIntSlice *[]int `default:"[1,2,3,4]"`
99+
PtrIntSliceSlice *[][]int `default:"[[1],[2],[3],[4]]"`
100+
PtrStringSliceSlice *[][]string `default:"[[1],[]]"`
101+
Float64PtrNoTag *float64
64102
}
65103

66104
func (s *DefaultsSuite) TestSetDefaultsBasic(c *C) {
@@ -106,6 +144,34 @@ func (s *DefaultsSuite) assertTypes(c *C, foo *ExampleBasic) {
106144
c.Assert(foo.IntSliceSlice, DeepEquals, [][]int{[]int{1}, []int{2}, []int{3}, []int{4}})
107145
c.Assert(foo.StringSliceSlice, DeepEquals, [][]string{[]string{"1"}, []string{}})
108146
c.Assert(foo.DateTime, Equals, "2020-08-10 12:55:10")
147+
c.Assert(*foo.BoolPtr, Equals, false)
148+
c.Assert(*foo.IntPtr, Equals, 33)
149+
c.Assert(*foo.Int8Ptr, Equals, int8(8))
150+
c.Assert(*foo.Int16Ptr, Equals, int16(16))
151+
c.Assert(*foo.Int32Ptr, Equals, int32(32))
152+
c.Assert(*foo.Int64Ptr, Equals, int64(64))
153+
c.Assert(*foo.UIntPtr, Equals, uint(11))
154+
c.Assert(*foo.UInt8Ptr, Equals, uint8(18))
155+
c.Assert(*foo.UInt16Ptr, Equals, uint16(116))
156+
c.Assert(*foo.UInt32Ptr, Equals, uint32(132))
157+
c.Assert(*foo.UInt64Ptr, Equals, uint64(164))
158+
c.Assert(*foo.Float32Ptr, Equals, float32(3.2))
159+
c.Assert(*foo.Float64Ptr, Equals, 6.4)
160+
c.Assert(*foo.DurationPtr, Equals, time.Second)
161+
c.Assert(*foo.SecondPtr, Equals, time.Second)
162+
c.Assert(foo.StructPtr.Bool, Equals, true)
163+
c.Assert(*foo.StructPtr.Integer, Equals, 33)
164+
c.Assert((*foo.PtrStructPtr).Bool, Equals, false)
165+
c.Assert(*(*foo.PtrStructPtr).Integer, Equals, 33)
166+
c.Assert(foo.ChildrenPtr, IsNil)
167+
c.Assert(*foo.PtrChildrenPtr, IsNil)
168+
c.Assert(**foo.PtrPtrChildrenPtr, IsNil)
169+
c.Assert(*foo.PtrStringSliceNoTag, IsNil)
170+
c.Assert(*foo.PtrStringSlice, DeepEquals, []string{"1", "2", "3", "4"})
171+
c.Assert(*foo.PtrIntSlice, DeepEquals, []int{1, 2, 3, 4})
172+
c.Assert(*foo.PtrIntSliceSlice, DeepEquals, [][]int{[]int{1}, []int{2}, []int{3}, []int{4}})
173+
c.Assert(*foo.PtrStringSliceSlice, DeepEquals, [][]string{[]string{"1"}, []string{}})
174+
c.Assert(foo.Float64PtrNoTag, IsNil)
109175
}
110176

111177
func (s *DefaultsSuite) TestSetDefaultsWithValues(c *C) {
@@ -118,6 +184,13 @@ func (s *DefaultsSuite) TestSetDefaultsWithValues(c *C) {
118184
Children: []Child{{Name: "alice"}, {Name: "bob", Age: 2}},
119185
}
120186

187+
intzero := 0
188+
foo.IntPtr = &intzero
189+
190+
ageZero := 0
191+
childPtr := &ChildPtr{Age: &ageZero}
192+
foo.ChildrenPtr = append(foo.ChildrenPtr, childPtr)
193+
121194
SetDefaults(foo)
122195

123196
c.Assert(foo.Integer, Equals, 55)
@@ -127,6 +200,10 @@ func (s *DefaultsSuite) TestSetDefaultsWithValues(c *C) {
127200
c.Assert(string(foo.Bytes), Equals, "foo")
128201
c.Assert(foo.Children[0].Age, Equals, 10)
129202
c.Assert(foo.Children[1].Age, Equals, 2)
203+
c.Assert(*foo.ChildrenPtr[0].Age, Equals, 0)
204+
c.Assert(foo.ChildrenPtr[0].Name, IsNil)
205+
206+
c.Assert(*foo.IntPtr, Equals, 0)
130207
}
131208

132209
func (s *DefaultsSuite) BenchmarkLogic(c *C) {

filler.go

+15-5
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,23 @@ func (f *Filler) isEmpty(field *FieldData) bool {
8282
// always assume the structs in the slice is empty and can be filled
8383
// the actually struct filling logic should take care of the rest
8484
return true
85+
case reflect.Ptr:
86+
switch field.Value.Type().Elem().Elem().Kind() {
87+
case reflect.Struct:
88+
return true
89+
default:
90+
return field.Value.Len() == 0
91+
}
8592
default:
8693
return field.Value.Len() == 0
8794
}
8895
case reflect.String:
8996
return field.Value.String() == ""
97+
case reflect.Ptr:
98+
if field.Value.Type().Elem().Kind() == reflect.Struct {
99+
return true
100+
}
101+
return field.Value.IsNil()
90102
}
91103
return true
92104
}
@@ -105,28 +117,26 @@ func (f *Filler) SetDefaultValue(field *FieldData) {
105117
return
106118
}
107119
}
108-
109-
return
110120
}
111121

112122
func (f *Filler) getFunctionByName(field *FieldData) FillerFunc {
113-
if f, ok := f.FuncByName[field.Field.Name]; ok == true {
123+
if f, ok := f.FuncByName[field.Field.Name]; ok {
114124
return f
115125
}
116126

117127
return nil
118128
}
119129

120130
func (f *Filler) getFunctionByType(field *FieldData) FillerFunc {
121-
if f, ok := f.FuncByType[GetTypeHash(field.Field.Type)]; ok == true {
131+
if f, ok := f.FuncByType[GetTypeHash(field.Field.Type)]; ok {
122132
return f
123133
}
124134

125135
return nil
126136
}
127137

128138
func (f *Filler) getFunctionByKind(field *FieldData) FillerFunc {
129-
if f, ok := f.FuncByKind[field.Field.Type.Kind()]; ok == true {
139+
if f, ok := f.FuncByKind[field.Field.Type.Kind()]; ok {
130140
return f
131141
}
132142

0 commit comments

Comments
 (0)