This project is archived and is in readonly mode.

#901 ✓hold
Lee Briggs

Support application/json request type in Request.JSON

Reported by Lee Briggs | May 5th, 2010 @ 10:03 PM | in 2.0 (closed)

Ajax request using Request.JSON

If submitting post data with request type "application/json", the data must be passed as a pre-serialized JSON string rather than an object.

The code makes the assumption that if an object is passed, it should be converted using .toQueryString(), which is correct for the default 'application/x-www-form-urlencoded', but not for 'application/json'.

Note, discovered whilst developing code to use asp.net services, which seem to require this request mime type, to then respond in JSON.

Comments and changes to this ticket

  • fakedarren

    fakedarren May 5th, 2010 @ 10:06 PM

    • State changed from “new” to “open”
    • Assigned user set to “fakedarren”
  • Fábio M. Costa

    Fábio M. Costa May 6th, 2010 @ 02:30 PM

    Request.JSON is intended to get the response as a json object, not the sent data.
    If you want to send a pre-serialized JSON string as your data you can still use the JSON.encode({"my": "object"}) to encode your object as a json string and send it as the data of you request.json.

  • Lee Briggs

    Lee Briggs May 6th, 2010 @ 06:11 PM

    An example, when using an ASP service with JSON data you currently have to do something like this.

    var request = Request.JSON({ headers: { 'Content-Type': 'application/json' }, urlEncoded: false });
    request.post(JSON.encode({id: 1, name: 'JohnDoe'));

    Ideally, this or better should work.

    var request = Request.JSON({ headers: { 'Content-Type': 'application/json' });
    request.post({id: 4, name: 'JohnDoe'});

    With the class encoding the data in the correct manner for the mime type.

  • Fábio M. Costa

    Fábio M. Costa May 9th, 2010 @ 12:24 AM

    • State changed from “open” to “invalid”

    Passing an object as the data of a Request will transform it into a QueryString.

  • Lee Briggs

    Lee Briggs May 10th, 2010 @ 09:28 PM

    I'm afraid transforming it into a QueryString in this case is the bug, not the solution.

    The problem is that the conversion of an object ignores the content type. If the Content-Type header has been set to application/json (as required by asp services) the post data should be serialized using json, NOT a querystring.

    As it is, ASP servers will receive the parameters as scrambled post data and fail to respond.

    Please reopen this bug, or close and indicate that this will not be supported (rather than invalid).

  • Fábio M. Costa

    Fábio M. Costa May 10th, 2010 @ 10:13 PM

    • State changed from “invalid” to “hold”
    • Assigned user changed from “fakedarren” to “Fábio M. Costa”

    Ok, do you have a live example so i can understand better and investigate the problem?
    Sorry for any misunderstanding.

  • Lee Briggs

    Lee Briggs May 10th, 2010 @ 10:47 PM

    NP, and thanks for looking into this.

    I can't send a link to my project due to contract, but the listings in this article may help a little - http://www.codeproject.com/Articles/38999/Consuming-ASP-net-WebServ...

    Compare listing 5, which retrieves xml, to listing 6 which does the same thing in json, and then listing 7 which does a more complex request.

    If you use PHP server side, there is no native support for post data in this format, but you can decode it yourself like this.

    if ($_SERVER['HTTP_X_REQUEST'] == 'JSON') {

    $post = json_decode(file_get_contents("php://input"), true);
    

    }

  • fakedarren

    fakedarren May 10th, 2010 @ 11:32 PM

    • Assigned user changed from “Fábio M. Costa” to “fakedarren”

    Lee,

    Get in touch (darren.waddell@gmail.com). It'd be really useful to see your setup if possible. I live in London so can sign non-disclosure agreements if need be. Hopefully we can help.

    Cheers
    Darren

  • Jason Petrik

    Jason Petrik June 23rd, 2010 @ 06:20 PM

    For example:

    I have an ASP.NET Web service called Services.asmx in it is a method with signature:

    Public Function GetForumMessage(ByVal message_id As Int32) As String
    

    To access the web service one needs to POST to Services.asmx/GetForumMessage with a body of:

    '{ "message_id": "62" }'
    not
    message_id=62
    

    The Content-Type also needs to be set to application/json.

    I wrote a class to handle all this for me:

    Request.ASP = new Class({
        Extends: Request,
        
        options: {
            secure: true,
            urlEncoded: false,
            method: "post"
        },
        
        initialize: function(options)
        {
            this.parent(options);
            this.headers.extend({ "Accept": "application/json", "X-Request": "JSON", "Content-Type": "application/json" });
        },
        
        success: function(text)
        {
            this.response.json = JSON.decode(text, this.options.secure);
            if (this.response.json && this.response.json.d) this.response.json = this.response.json.d;
            this.onSuccess(this.response.json, text);
        },
        
        send: function(options)
        {
            if ($type(options) != "string") options = JSON.encode(options);
            return this.parent(options);
        }
        
    });
    

    The problem now is when other options from Request are used, such as noCache. If noCache is true then send will POST:

    noCache=123456789&'{ "message_id": "62" }'
    

    I would like to see in mootools Request a jsonEncoded option similar to urlEncoded. It will set the Content-Type header to application/json and will append noCache and other data correctly.

  • Jason Petrik

    Jason Petrik June 27th, 2010 @ 07:58 PM

    • Milestone order changed from “0” to “0”

    Below is an untested version but should make clear what I am trying to accomplish. Unfortunately I was unable to come up with a version that wasn't dependent on String.QueryString from mootools more.

    send: function(options){
        if (!this.check(options)) return this;
        this.running = true;
    
        var type = $type(options);
        if (type == 'element') options = {data: options.toQueryString().parseQueryString()};
        if (type == 'string') options = {data: options.parseQueryString()};
    
        var old = this.options;
        options = $extend({data: old.data, url: old.url, method: old.method}, options);
        var data = options.data, url = String(options.url), method = options.method.toLowerCase();
        
        if (this.options.format) data.format = this.options.format;
        
        if (this.options.emulation && !['get', 'post'].contains(method)){
            data._method = method;
            method = 'post';
        }
        
        if (this.options.urlEncoded && method == 'post'){
            var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : '';
            this.headers.set('Content-type', 'application/x-www-form-urlencoded' + encoding);
        }
        
        if (this.options.noCache) data.noCache = new Date().getTime();
        
        if (this.options.jsonEncoded){
            var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : '';
            this.headers.set('Content-type', 'application/json' + encoding);
            data = JSON.encode(data);
        } else {
            data = data.toQueryString();
        }
        
        var trimPosition = url.lastIndexOf('/');
        if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition);
    
        if (data && method == 'get'){
            url = url + (url.contains('?') ? '&' : '?') + data;
            data = null;
        }
        
        this.xhr.open(method.toUpperCase(), url, this.options.async);
    
        this.xhr.onreadystatechange = this.onStateChange.bind(this);
    
        this.headers.each(function(value, key){
            try {
                this.xhr.setRequestHeader(key, value);
            } catch (e){
                this.fireEvent('exception', [key, value]);
            }
        }, this);
    
        this.fireEvent('request');
        this.xhr.send(data);
        if (!this.options.async) this.onStateChange();
        return this;
    }
    
  • Christoph Pojer

    Christoph Pojer November 9th, 2010 @ 07:25 PM

    • State changed from “hold” to “wontfix”
  • litemotiv

    litemotiv January 5th, 2011 @ 07:38 PM

    Christoph, can you give more information about why this ticket was changed to wontfix?

    I'm running into this issue too, in my case with the XBMC jsonrpc backend. It expects a JSON object like this:

    myRequest = new Request.JSON(
    {
        url: "/jsonrpc",
        urlEncoded: false
    }).post(
    {
        "jsonrpc": "2.0",
        "method": "Files.GetSources",
        "id": 1,
        "params": 
        {
            "media": "video"
        }
    });
    

    Yet Request automatically converts any data to querystring. Wouldn't it be logical if a JSON request class at least has an option to also send data in JSON format, or perhaps even have that as a default setting which can be overridden to querystring?

  • Sebastian Markbåge

    Sebastian Markbåge January 5th, 2011 @ 11:28 PM

    • Milestone set to 2.0
    • State changed from “wontfix” to “hold”
    • Assigned user changed from “fakedarren” to “Sebastian Markbåge”
    • Milestone order changed from “755” to “0”

    This is a real issue and very inconvenient for people trying to use Request with these services. I'm reopening as "on hold" for 2.0.

    Fabio had a suggestion to handle response mime types automatically for Request in 2.0 - instead of having specialized subclasses. The same model could be extended to request types.

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile »

Shared Ticket Bins

Pages