3

Revel doesn't parse JSON parameter when the content type is "application/json".

How to enforce this ?

Example :

http://localhost:9000/foundations in POST call Foundations.Create function. Into this function I use fmt.Println("Params :", c.Params) to check parameters

Ruby POST JSON data

#!/usr/bin/env ruby
require "rubygems"
require "json"
require "net/https"

uri = URI.parse("http://localhost:9000/foundations")
http = Net::HTTP.new(uri.host, uri.port)

header = { "Content-Type" => "application/json" }

req = Net::HTTP::Post.new(uri.path, header)
req.body = { 
  "data" => "mysurface"
}.to_json()

res = http.start { |http| http.request(req) }

The debug print is Params : &{map[] map[] map[] map[] map[] map[] []}

And when I use none "application/json" like : curl -F 'data=mysurface' http://127.0.0.1:9000/foundations

The print is : Params : &{map[data:[mysurface]] map[] map[] map[] map[data:[mysurface]] map[] []}

Manawasp
  • 517
  • 6
  • 14

1 Answers1

2

The issue is that with json, there is no way for Revel to really handle it in the same way it does with a "normal" post request. Normally, it can just bind each param into an map[string]string object. But with JSON, it has to be able to handle arrays and nested objects, and there is no good way of doing that without knowing beforehand what the structure of the JSON will be.

So the solution, is to handle it yourself - you should know what fields to expect, so create a struct with those fields, and json.Unmarshal into it.

import (
    "encoding/json"
    "fmt"
)

type Surface struct {
    Data string `json:"data"` //since we have to export the field
                              //but want the lowercase letter
}

func (c MyController) Action() revel.Result {
    var s Surface
    err := json.Unmarshal([]byte(c.Request.Body), &s)    
    fmt.Println(s.Data) //mysurface
}

If you don't like using the json:"data" tag or don't want to export your field, you could also write your own UnmarshalJSON function

type Surface struct {
    data string `json:"data"` //can use unexported field
                              //since we handle JSON ourselves
}

func (s *Structure) UnmarshalJSON(data []byte) error {
    if (s == nil) {
        return errors.New("Structure: UnmarshalJSON on nil pointer")
    }
    var fields map[string]string
    json.Unmarshal(data, &fields)    
    *s.data = fields["data"]
    return nil
}
dave
  • 62,300
  • 5
  • 72
  • 93
  • I just tried this and got a build error `cannot convert c.Controller.Request.Request.Body (type io.ReadCloser) to type []byte` – Mark Richman Jun 16 '16 at 23:37
  • @MarkRichman - haven't used revel in a while so it looks like things changed, if it's an io.ReadCloser though you can do `buf := new(bytes.Buffer) buf.ReadFrom(c.Controller.Request.Request.Body) data := buf.Bytes()` – dave Jun 17 '16 at 01:11
  • 1
    I just did `content, _ := ioutil.ReadAll(c.Request.Body)` and then `err := json.Unmarshal([]byte(content), &mystruct)` with success. – Mark Richman Jun 17 '16 at 01:12