diff --git a/HISTORY.md b/HISTORY.md
index eecf76e3..0b071f20 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -2,6 +2,34 @@
**How to upgrade**: remove your `$GOPATH/src/github.com/kataras` folder, open your command-line and execute this command: `go get -u github.com/kataras/iris/iris`.
+## 4.4.7 -> 4.4.8
+
+- **NEW**: `RequestDecoder` gives the ability to set a custom decoder **per passed object** when `context.ReadJSON` and `context.ReadXML`
+
+```go
+// BodyDecoder is an interface which any struct can implement in order to customize the decode action
+// from ReadJSON and ReadXML
+//
+// Trivial example of this could be:
+// type User struct { Username string }
+//
+// func (u *User) Decode(data []byte) error {
+// return json.Unmarshal(data, u)
+// }
+//
+// the 'context.ReadJSON/ReadXML(&User{})' will call the User's
+// Decode option to decode the request body
+//
+// Note: This is totally optionally, the default decoders
+// for ReadJSON is the encoding/json and for ReadXML is the encoding/xml
+type BodyDecoder interface {
+ Decode(data []byte) error
+}
+
+```
+
+> for a usage example go to https://github.com/kataras/iris/blob/master/context_test.go#L262
+
## 4.4.6 -> 4.4.7
- **small fix**: websocket server is nil when more than the default websocket station tries to be registered before `OnConnection` called[*](https://github.com/kataras/iris/issues/460)
diff --git a/README.md b/README.md
index b2b34f3c..d796f206 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@
-
+
@@ -870,7 +870,7 @@ I recommend writing your API tests using this new library, [httpexpect](https://
Versioning
------------
-Current: **v4.4.7**
+Current: **v4.4.8**
> Iris is an active project
@@ -906,7 +906,7 @@ This project is licensed under the [MIT License](LICENSE), Copyright (c) 2016 Ge
[Travis]: http://travis-ci.org/kataras/iris
[License Widget]: https://img.shields.io/badge/license-MIT%20%20License%20-E91E63.svg?style=flat-square
[License]: https://github.com/kataras/iris/blob/master/LICENSE
-[Release Widget]: https://img.shields.io/badge/release-4.4.7%20-blue.svg?style=flat-square
+[Release Widget]: https://img.shields.io/badge/release-4.4.8%20-blue.svg?style=flat-square
[Release]: https://github.com/kataras/iris/releases
[Chat Widget]: https://img.shields.io/badge/community-chat%20-00BCD4.svg?style=flat-square
[Chat]: https://kataras.rocket.chat/channel/iris
diff --git a/context.go b/context.go
index 1f7fdbda..e56ad261 100644
--- a/context.go
+++ b/context.go
@@ -371,33 +371,65 @@ func (ctx *Context) Subdomain() (subdomain string) {
return
}
+// BodyDecoder is an interface which any struct can implement in order to customize the decode action
+// from ReadJSON and ReadXML
+//
+// Trivial example of this could be:
+// type User struct { Username string }
+//
+// func (u *User) Decode(data []byte) error {
+// return json.Unmarshal(data, u)
+// }
+//
+// the 'context.ReadJSON/ReadXML(&User{})' will call the User's
+// Decode option to decode the request body
+//
+// Note: This is totally optionally, the default decoders
+// for ReadJSON is the encoding/json and for ReadXML is the encoding/xml
+type BodyDecoder interface {
+ Decode(data []byte) error
+}
+
// ReadJSON reads JSON from request's body
func (ctx *Context) ReadJSON(jsonObject interface{}) error {
- data := ctx.RequestCtx.Request.Body()
+ rawData := ctx.Request.Body()
- decoder := json.NewDecoder(strings.NewReader(string(data)))
- err := decoder.Decode(jsonObject)
-
- //err != nil fix by @shiena
- if err != nil && err != io.EOF {
- return errReadBody.Format("JSON", err.Error())
+ // check if the jsonObject contains its own decode
+ // in this case the jsonObject should be a pointer also,
+ // but this is up to the user's custom Decode implementation*
+ //
+ // See 'BodyDecoder' for more
+ if decoder, isDecoder := jsonObject.(BodyDecoder); isDecoder {
+ return decoder.Decode(rawData)
}
- return nil
+ // check if jsonObject is already a pointer, if yes then pass as it's
+ if reflect.TypeOf(jsonObject).Kind() == reflect.Ptr {
+ return json.Unmarshal(rawData, jsonObject)
+ }
+ // finally, if the jsonObject doesn't contains a self-body decoder and it's not a pointer
+ return json.Unmarshal(rawData, &jsonObject)
}
// ReadXML reads XML from request's body
func (ctx *Context) ReadXML(xmlObject interface{}) error {
- data := ctx.RequestCtx.Request.Body()
+ rawData := ctx.Request.Body()
- decoder := xml.NewDecoder(strings.NewReader(string(data)))
- err := decoder.Decode(xmlObject)
- //err != nil fix by @shiena
- if err != nil && err != io.EOF {
- return errReadBody.Format("XML", err.Error())
+ // check if the xmlObject contains its own decode
+ // in this case the jsonObject should be a pointer also,
+ // but this is up to the user's custom Decode implementation*
+ //
+ // See 'BodyDecoder' for more
+ if decoder, isDecoder := xmlObject.(BodyDecoder); isDecoder {
+ return decoder.Decode(rawData)
}
- return nil
+ // check if xmlObject is already a pointer, if yes then pass as it's
+ if reflect.TypeOf(xmlObject).Kind() == reflect.Ptr {
+ return xml.Unmarshal(rawData, xmlObject)
+ }
+ // finally, if the xmlObject doesn't contains a self-body decoder and it's not a pointer
+ return xml.Unmarshal(rawData, &xmlObject)
}
// ReadForm binds the formObject with the form data
diff --git a/context_test.go b/context_test.go
index 96354959..b27a7265 100644
--- a/context_test.go
+++ b/context_test.go
@@ -11,7 +11,9 @@ CONTRIBUTE & DISCUSSION ABOUT TESTS TO: https://github.com/iris-contrib/tests
*/
import (
+ "encoding/json"
"encoding/xml"
+ "fmt"
"net/url"
"strconv"
"strings"
@@ -240,13 +242,76 @@ func TestContextReadJSON(t *testing.T) {
ctx.JSON(StatusOK, obj)
})
+ Post("/json_pointer", func(ctx *Context) {
+ obj := &testBinderData{}
+ err := ctx.ReadJSON(obj)
+ if err != nil {
+ t.Fatalf("Error when parsing the JSON body: %s", err.Error())
+ }
+ ctx.JSON(StatusOK, obj)
+ })
+
e := Tester(t)
passed := map[string]interface{}{"Username": "myusername", "Mail": "mymail@iris-go.com", "mydata": []string{"mydata1", "mydata2"}}
expectedObject := testBinderData{Username: "myusername", Mail: "mymail@iris-go.com", Data: []string{"mydata1", "mydata2"}}
e.POST("/json").WithJSON(passed).Expect().Status(StatusOK).JSON().Object().Equal(expectedObject)
+ e.POST("/json_pointer").WithJSON(passed).Expect().Status(StatusOK).JSON().Object().Equal(expectedObject)
}
+type testJSONBinderDataWithDecoder struct {
+ Username string
+ Mail string
+ Data []string `json:"mydata"`
+ shouldError bool
+}
+
+func (tj *testJSONBinderDataWithDecoder) Decode(data []byte) error {
+ if tj.shouldError {
+ return fmt.Errorf("Should error")
+ }
+ return json.Unmarshal(data, tj)
+}
+
+func TestContextReadJSONWithDecoder(t *testing.T) {
+ initDefault()
+ Post("/json_should_error", func(ctx *Context) {
+ obj := testJSONBinderDataWithDecoder{shouldError: true}
+ err := ctx.ReadJSON(&obj)
+ if err == nil {
+ t.Fatalf("Should prompted for error 'Should error' but not error returned from the custom decoder!")
+ }
+ ctx.Write(err.Error())
+ ctx.SetStatusCode(StatusOK)
+ })
+
+ Post("/json", func(ctx *Context) {
+ obj := testJSONBinderDataWithDecoder{}
+ err := ctx.ReadJSON(&obj)
+ if err != nil {
+ t.Fatalf("Error when parsing the JSON body: %s", err.Error())
+ }
+ ctx.JSON(StatusOK, obj)
+ })
+
+ Post("/json_pointer", func(ctx *Context) {
+ obj := &testJSONBinderDataWithDecoder{}
+ err := ctx.ReadJSON(obj)
+ if err != nil {
+ t.Fatalf("Error when parsing the JSON body: %s", err.Error())
+ }
+ ctx.JSON(StatusOK, obj)
+ })
+
+ e := Tester(t)
+ passed := map[string]interface{}{"Username": "kataras", "Mail": "mymail@iris-go.com", "mydata": []string{"mydata1", "mydata2"}}
+ expectedObject := testJSONBinderDataWithDecoder{Username: "kataras", Mail: "mymail@iris-go.com", Data: []string{"mydata1", "mydata2"}}
+
+ e.POST("/json_should_error").WithJSON(passed).Expect().Status(StatusOK).Body().Equal("Should error")
+ e.POST("/json").WithJSON(passed).Expect().Status(StatusOK).JSON().Object().Equal(expectedObject)
+ e.POST("/json_pointer").WithJSON(passed).Expect().Status(StatusOK).JSON().Object().Equal(expectedObject)
+} // no need for xml, it's exact the same.
+
func TestContextReadXML(t *testing.T) {
initDefault()
diff --git a/iris.go b/iris.go
index 5cb56f38..69664982 100644
--- a/iris.go
+++ b/iris.go
@@ -79,7 +79,7 @@ import (
const (
// Version is the current version of the Iris web framework
- Version = "4.4.7"
+ Version = "4.4.8"
banner = ` _____ _
|_ _| (_)