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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy