Skip to content

Commit fa40869

Browse files
authored
Merge pull request revel#1173 from notzippy/json-params
JSON Parameter binding
2 parents 523f6c6 + 4e58af4 commit fa40869

File tree

4 files changed

+132
-39
lines changed

4 files changed

+132
-39
lines changed

binder.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package revel
66

77
import (
8+
"encoding/json"
89
"fmt"
910
"io"
1011
"io/ioutil"
@@ -290,7 +291,15 @@ func unbindSlice(output map[string]string, name string, val interface{}) {
290291
}
291292

292293
func bindStruct(params *Params, name string, typ reflect.Type) reflect.Value {
293-
result := reflect.New(typ).Elem()
294+
resultPointer := reflect.New(typ)
295+
result := resultPointer.Elem()
296+
if params.JSON != nil {
297+
// Try to inject the response as a json into the created result
298+
if err := json.Unmarshal(params.JSON, resultPointer.Interface()); err != nil {
299+
WARN.Println("W: bindStruct: Unable to unmarshal request:", name, err)
300+
}
301+
return result
302+
}
294303
fieldValues := make(map[string]reflect.Value)
295304
for key := range params.Values {
296305
if !strings.HasPrefix(key, name+".") {
@@ -407,10 +416,20 @@ func bindReadSeeker(params *Params, name string, typ reflect.Type) reflect.Value
407416
// params["a[5]"]=foo, name="a", typ=map[int]string => map[int]string{5: "foo"}
408417
func bindMap(params *Params, name string, typ reflect.Type) reflect.Value {
409418
var (
410-
result = reflect.MakeMap(typ)
411419
keyType = typ.Key()
412420
valueType = typ.Elem()
421+
resultPtr = reflect.New(reflect.MapOf(keyType, valueType))
422+
result = resultPtr.Elem()
413423
)
424+
result.Set(reflect.MakeMap(typ))
425+
if params.JSON != nil {
426+
// Try to inject the response as a json into the created result
427+
if err := json.Unmarshal(params.JSON, resultPtr.Interface()); err != nil {
428+
WARN.Println("W: bindMap: Unable to unmarshal request:", name, err)
429+
}
430+
return result
431+
}
432+
414433
for paramName, values := range params.Values {
415434
if !strings.HasPrefix(paramName, name+"[") || paramName[len(paramName)-1] != ']' {
416435
continue

binder_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package revel
66

77
import (
8+
"encoding/json"
89
"fmt"
910
"io"
1011
"io/ioutil"
@@ -165,6 +166,33 @@ var fileBindings = []struct{ val, arrval, f interface{} }{
165166
{(*io.ReadSeeker)(nil), []io.ReadSeeker{}, ioutil.ReadAll},
166167
}
167168

169+
func TestJsonBinder(t *testing.T) {
170+
// create a structure to be populated
171+
{
172+
d, _ := json.Marshal(map[string]int{"a": 1})
173+
params := &Params{JSON: d}
174+
foo := struct{ A int }{}
175+
ParseParams(params, NewRequest(getMultipartRequest()))
176+
actual := Bind(params, "test", reflect.TypeOf(foo))
177+
valEq(t, "TestJsonBinder", reflect.ValueOf(actual.Interface().(struct{ A int }).A), reflect.ValueOf(1))
178+
}
179+
{
180+
d, _ := json.Marshal(map[string]interface{}{"a": map[string]int{"b": 45}})
181+
params := &Params{JSON: d}
182+
testMap := map[string]interface{}{}
183+
actual := Bind(params, "test", reflect.TypeOf(testMap)).Interface().(map[string]interface{})
184+
if actual["a"].(map[string]interface{})["b"].(float64) != 45 {
185+
t.Errorf("Failed to fetch map value %#v", actual["a"])
186+
}
187+
// Check to see if a named map works
188+
actualb := Bind(params, "test", reflect.TypeOf(map[string]map[string]float64{})).Interface().(map[string]map[string]float64)
189+
if actualb["a"]["b"] != 45 {
190+
t.Errorf("Failed to fetch map value %#v", actual["a"])
191+
}
192+
193+
}
194+
}
195+
168196
func TestBinder(t *testing.T) {
169197
// Reuse the mvc_test.go multipart request to test the binder.
170198
params := &Params{}

params.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55
package revel
66

77
import (
8+
"encoding/json"
9+
"io/ioutil"
810
"mime/multipart"
911
"net/url"
1012
"os"
1113
"reflect"
14+
"errors"
1215
)
1316

1417
// Params provides a unified view of the request params.
@@ -31,6 +34,7 @@ type Params struct {
3134

3235
Files map[string][]*multipart.FileHeader // Files uploaded in a multipart form
3336
tmpFiles []*os.File // Temp files used during the request.
37+
JSON []byte // JSON data from request body
3438
}
3539

3640
// ParseParams parses the `http.Request` params into `revel.Controller.Params`
@@ -56,6 +60,19 @@ func ParseParams(params *Params, req *Request) {
5660
params.Form = req.MultipartForm.Value
5761
params.Files = req.MultipartForm.File
5862
}
63+
case "application/json":
64+
fallthrough
65+
case "text/json":
66+
if req.Body != nil {
67+
if content, err := ioutil.ReadAll(req.Body); err == nil {
68+
// We wont bind it until we determine what we are binding too
69+
params.JSON = content
70+
} else {
71+
ERROR.Println("Failed to ready request body bytes", err)
72+
}
73+
} else {
74+
INFO.Println("Json post received with empty body")
75+
}
5976
}
6077

6178
params.Values = params.calcValues()
@@ -73,7 +90,29 @@ func (p *Params) Bind(dest interface{}, name string) {
7390
if !value.CanSet() {
7491
panic("revel/params: non-settable variable passed to Bind: " + name)
7592
}
93+
94+
// Remove the json from the Params, this will stop the binder from attempting
95+
// to use the json data to populate the destination interface. We do not want
96+
// to do this on a named bind directly against the param, it is ok to happen when
97+
// the action is invoked.
98+
jsonData := p.JSON
99+
p.JSON = nil
76100
value.Set(Bind(p, name, value.Type()))
101+
p.JSON = jsonData
102+
}
103+
104+
// Bind binds the JSON data to the dest.
105+
func (p *Params) BindJSON(dest interface{}) error {
106+
value := reflect.ValueOf(dest)
107+
if value.Kind() != reflect.Ptr {
108+
WARN.Println("BindJSON not a pointer")
109+
return errors.New("BindJSON not a pointer")
110+
}
111+
if err := json.Unmarshal(p.JSON, dest); err != nil {
112+
WARN.Println("W: bindMap: Unable to unmarshal request:", err)
113+
return err
114+
}
115+
return nil
77116
}
78117

79118
// calcValues returns a unified view of the component param maps.

validation.go

Lines changed: 44 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -185,49 +185,56 @@ func (v *Validation) Check(obj interface{}, checks ...Validator) *ValidationResu
185185

186186
// ValidationFilter revel Filter function to be hooked into the filter chain.
187187
func ValidationFilter(c *Controller, fc []Filter) {
188-
errors, err := restoreValidationErrors(c.Request.Request)
189-
c.Validation = &Validation{
190-
Errors: errors,
191-
keep: false,
192-
}
193-
hasCookie := (err != http.ErrNoCookie)
188+
// If json request, we shall assume json response is intended,
189+
// as such no validation cookies should be tied response
190+
if c.Params != nil && c.Params.JSON != nil {
191+
c.Validation = &Validation{}
192+
fc[0](c, fc[1:])
193+
} else {
194+
errors, err := restoreValidationErrors(c.Request.Request)
195+
c.Validation = &Validation{
196+
Errors: errors,
197+
keep: false,
198+
}
199+
hasCookie := (err != http.ErrNoCookie)
194200

195-
fc[0](c, fc[1:])
201+
fc[0](c, fc[1:])
196202

197-
// Add Validation errors to ViewArgs.
198-
c.ViewArgs["errors"] = c.Validation.ErrorMap()
203+
// Add Validation errors to ViewArgs.
204+
c.ViewArgs["errors"] = c.Validation.ErrorMap()
199205

200-
// Store the Validation errors
201-
var errorsValue string
202-
if c.Validation.keep {
203-
for _, error := range c.Validation.Errors {
204-
if error.Message != "" {
205-
errorsValue += "\x00" + error.Key + ":" + error.Message + "\x00"
206+
// Store the Validation errors
207+
var errorsValue string
208+
if c.Validation.keep {
209+
for _, err := range c.Validation.Errors {
210+
if err.Message != "" {
211+
errorsValue += "\x00" + err.Key + ":" + err.Message + "\x00"
212+
}
206213
}
207214
}
208-
}
209215

210-
// When there are errors from Validation and Keep() has been called, store the
211-
// values in a cookie. If there previously was a cookie but no errors, remove
212-
// the cookie.
213-
if errorsValue != "" {
214-
c.SetCookie(&http.Cookie{
215-
Name: CookiePrefix + "_ERRORS",
216-
Value: url.QueryEscape(errorsValue),
217-
Domain: CookieDomain,
218-
Path: "/",
219-
HttpOnly: true,
220-
Secure: CookieSecure,
221-
})
222-
} else if hasCookie {
223-
c.SetCookie(&http.Cookie{
224-
Name: CookiePrefix + "_ERRORS",
225-
MaxAge: -1,
226-
Domain: CookieDomain,
227-
Path: "/",
228-
HttpOnly: true,
229-
Secure: CookieSecure,
230-
})
216+
// When there are errors from Validation and Keep() has been called, store the
217+
// values in a cookie. If there previously was a cookie but no errors, remove
218+
// the cookie.
219+
if errorsValue != "" {
220+
c.SetCookie(&http.Cookie{
221+
Name: CookiePrefix + "_ERRORS",
222+
Value: url.QueryEscape(errorsValue),
223+
Domain: CookieDomain,
224+
Path: "/",
225+
HttpOnly: true,
226+
Secure: CookieSecure,
227+
})
228+
} else if hasCookie {
229+
c.SetCookie(&http.Cookie{
230+
Name: CookiePrefix + "_ERRORS",
231+
MaxAge: -1,
232+
Domain: CookieDomain,
233+
Path: "/",
234+
HttpOnly: true,
235+
Secure: CookieSecure,
236+
})
237+
}
231238
}
232239
}
233240

0 commit comments

Comments
 (0)