Skip to content

Commit 920e9aa

Browse files
author
Dean Karn
committed
Add embedded struct support + options
1 parent 20d0406 commit 920e9aa

File tree

12 files changed

+256
-9
lines changed

12 files changed

+256
-9
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package form
22
============
3-
<img align="right" src="https://raw.githubusercontent.com/go-playground/form/master/logo.jpg">![Project status](https://img.shields.io/badge/version-2.3.0-green.svg)
3+
<img align="right" src="https://raw.githubusercontent.com/go-playground/form/master/logo.jpg">![Project status](https://img.shields.io/badge/version-3.0.0-green.svg)
44
[![Build Status](https://semaphoreci.com/api/v1/joeybloggs/form/branches/master/badge.svg)](https://semaphoreci.com/joeybloggs/form)
55
[![Coverage Status](https://coveralls.io/repos/github/go-playground/form/badge.svg?branch=master)](https://coveralls.io/github/go-playground/form?branch=master)
66
[![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/form)](https://goreportcard.com/report/github.com/go-playground/form)

_examples/decoder-embedded/main.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"net/url"
7+
8+
"github.com/go-playground/form"
9+
)
10+
11+
// A ...
12+
type A struct {
13+
Field string
14+
}
15+
16+
// B ...
17+
type B struct {
18+
A
19+
Field string
20+
}
21+
22+
// use a single instance of Decoder, it caches struct info
23+
var decoder *form.Decoder
24+
25+
func main() {
26+
decoder = form.NewDecoder()
27+
28+
// this simulates the results of http.Request's ParseForm() function
29+
values := parseFormB()
30+
31+
var b B
32+
33+
// must pass a pointer
34+
err := decoder.Decode(&b, values)
35+
if err != nil {
36+
log.Panic(err)
37+
}
38+
39+
fmt.Printf("%#v\n", b)
40+
41+
values = parseFormAB()
42+
43+
// must pass a pointer
44+
err = decoder.Decode(&b, values)
45+
if err != nil {
46+
log.Panic(err)
47+
}
48+
49+
fmt.Printf("%#v\n", b)
50+
}
51+
52+
// this simulates the results of http.Request's ParseForm() function
53+
func parseFormB() url.Values {
54+
return url.Values{
55+
"Field": []string{"B FieldVal"},
56+
}
57+
}
58+
59+
// this simulates the results of http.Request's ParseForm() function
60+
func parseFormAB() url.Values {
61+
return url.Values{
62+
"Field": []string{"B FieldVal"},
63+
"A.Field": []string{"A FieldVal"},
64+
}
65+
}
File renamed without changes.

_examples/encoder-embedded/main.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"log"
6+
7+
"github.com/go-playground/form"
8+
)
9+
10+
// A ...
11+
type A struct {
12+
Field string
13+
}
14+
15+
// B ...
16+
type B struct {
17+
A
18+
Field string
19+
}
20+
21+
// use a single instance of Encoder, it caches struct info
22+
var encoder *form.Encoder
23+
24+
func main() {
25+
26+
type A struct {
27+
Field string
28+
}
29+
30+
type B struct {
31+
A
32+
Field string
33+
}
34+
35+
b := B{
36+
A: A{
37+
Field: "A Val",
38+
},
39+
Field: "B Val",
40+
}
41+
42+
encoder = form.NewEncoder()
43+
44+
v, err := encoder.Encode(b)
45+
if err != nil {
46+
log.Panic(err)
47+
}
48+
49+
fmt.Printf("%#v\n", v)
50+
51+
encoder.SetAnonymousMode(form.AnonymousSeparate)
52+
v, err = encoder.Encode(b)
53+
if err != nil {
54+
log.Panic(err)
55+
}
56+
57+
fmt.Printf("%#v\n", v)
58+
}
File renamed without changes.

cache.go

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,34 @@ package form
22

33
import (
44
"reflect"
5+
"sort"
56
"strings"
67
"sync"
78
"sync/atomic"
89
)
910

11+
type cacheFields []cachedField
12+
13+
func (s cacheFields) Len() int {
14+
return len(s)
15+
}
16+
17+
func (s cacheFields) Less(i, j int) bool {
18+
return !s[i].isAnonymous
19+
}
20+
21+
func (s cacheFields) Swap(i, j int) {
22+
s[i], s[j] = s[j], s[i]
23+
}
24+
1025
type cachedField struct {
11-
idx int
12-
name string
26+
idx int
27+
name string
28+
isAnonymous bool
1329
}
1430

1531
type cachedStruct struct {
16-
fields []cachedField
32+
fields cacheFields
1733
}
1834

1935
type structCacheMap struct {
@@ -74,6 +90,7 @@ func (s *structCacheMap) parseStruct(mode Mode, current reflect.Value, key refle
7490

7591
fld = typ.Field(i)
7692

93+
// fmt.Println("PkgPath:", fld.PkgPath, " Anonymous:", fld.Anonymous, " Name:", fld.Name)
7794
if fld.PkgPath != blank && !fld.Anonymous {
7895
continue
7996
}
@@ -88,6 +105,7 @@ func (s *structCacheMap) parseStruct(mode Mode, current reflect.Value, key refle
88105
}
89106
}
90107

108+
// fmt.Println("Ignore:", name == ignore)
91109
if name == ignore {
92110
continue
93111
}
@@ -100,9 +118,10 @@ func (s *structCacheMap) parseStruct(mode Mode, current reflect.Value, key refle
100118
name = fld.Name
101119
}
102120

103-
cs.fields = append(cs.fields, cachedField{idx: i, name: name})
121+
cs.fields = append(cs.fields, cachedField{idx: i, name: name, isAnonymous: fld.Anonymous})
104122
}
105123

124+
sort.Sort(cs.fields)
106125
s.Set(typ, cs)
107126

108127
s.lock.Unlock()

decoder.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,12 @@ func (d *decoder) traverseStruct(v reflect.Value, typ reflect.Type, namespace []
156156

157157
namespace = namespace[:l]
158158

159+
if f.isAnonymous {
160+
if d.setFieldByType(v.Field(f.idx), namespace, 0) {
161+
set = true
162+
}
163+
}
164+
159165
if first {
160166
namespace = append(namespace, f.name...)
161167
} else {

decoder_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1506,3 +1506,38 @@ func TestDecoderRegisterTagNameFunc(t *testing.T) {
15061506
Equal(t, test.Value, "joeybloggs")
15071507
Equal(t, test.Ignore, "")
15081508
}
1509+
1510+
func TestDecoderEmbedModes(t *testing.T) {
1511+
1512+
type A struct {
1513+
Field string
1514+
}
1515+
1516+
type B struct {
1517+
A
1518+
Field string
1519+
}
1520+
1521+
var b B
1522+
1523+
decoder := NewDecoder()
1524+
1525+
values := url.Values{
1526+
"Field": []string{"Value"},
1527+
}
1528+
1529+
err := decoder.Decode(&b, values)
1530+
Equal(t, err, nil)
1531+
Equal(t, b.Field, "Value")
1532+
Equal(t, b.A.Field, "Value")
1533+
1534+
values = url.Values{
1535+
"Field": []string{"B Val"},
1536+
"A.Field": []string{"A Val"},
1537+
}
1538+
1539+
err = decoder.Decode(&b, values)
1540+
Equal(t, err, nil)
1541+
Equal(t, b.Field, "B Val")
1542+
Equal(t, b.A.Field, "A Val")
1543+
}

encoder.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,13 @@ func (e *encoder) traverseStruct(v reflect.Value, namespace []byte, idx int) {
4949
}
5050

5151
for _, f := range s.fields {
52-
5352
namespace = namespace[:l]
5453

54+
if f.isAnonymous && e.e.embedAnonymous {
55+
e.setFieldByType(v.Field(f.idx), namespace, idx)
56+
continue
57+
}
58+
5559
if first {
5660
namespace = append(namespace, f.name...)
5761
} else {

encoder_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1286,3 +1286,37 @@ func TestEncoderRegisterTagNameFunc(t *testing.T) {
12861286
Equal(t, len(values), 1)
12871287
Equal(t, values["name"][0], "Joeybloggs")
12881288
}
1289+
1290+
func TestEncoderEmbedModes(t *testing.T) {
1291+
1292+
type A struct {
1293+
Field string
1294+
}
1295+
1296+
type B struct {
1297+
A
1298+
Field string
1299+
}
1300+
1301+
b := B{
1302+
A: A{
1303+
Field: "A Val",
1304+
},
1305+
Field: "B Val",
1306+
}
1307+
1308+
encoder := NewEncoder()
1309+
1310+
values, err := encoder.Encode(b)
1311+
Equal(t, err, nil)
1312+
Equal(t, len(values), 1)
1313+
Equal(t, values["Field"][0], "B Val")
1314+
Equal(t, values["Field"][1], "A Val")
1315+
1316+
encoder.SetAnonymousMode(AnonymousSeparate)
1317+
values, err = encoder.Encode(b)
1318+
Equal(t, err, nil)
1319+
Equal(t, len(values), 2)
1320+
Equal(t, values["Field"][0], "B Val")
1321+
Equal(t, values["A.Field"][0], "A Val")
1322+
}

0 commit comments

Comments
 (0)