Search code examples
pythonjsonlistweb2py

Why don't web2py json services treat lists properly?


The following works for json whose outermost container is an object like { ... }

@service.json
def index():
    data = request.vars
    #fields are now accessible via data["fieldname"] or data.fieldname
    #processing must be done to turn Storage object into dict()
    return data_as_dict

If you post a list however, it does not work

POST:

[
    {"test": 1}
]

data will be an empty Storage object and data[0] will be None

The workaround is simple:

@service.json      #so output is still returned as json
def index():
    data = json.loads(request.body.read())
    return data

data is now a dict in cases of object style JSON (easier to work with than a Storage object imo) and a native list when the JSON is a list.

My question is why is this not the default behaviour? Why should a JSON service not accept valid JSON?


Solution

  • The @service.json decorator simply registers a function so it can be accessed via a controller that returns a Service object. The decorator ensures that the service controller returns a JSON response when the decorated function is called, but it does nothing regarding the processing of JSON input.

    In any case, your problem is not with the @service.json decorator but with a misunderstanding regarding request.vars. request.vars is a dictionary-like object that is populated with keys and values from the query string and/or the request body (if the request body includes form variables or a JSON object of keys and values). It is not intended to simply be a copy of any arbitrary data structure that is posted in the request body. So, if you post a JSON array in the request body, it would not make sense to copy that array to request.vars, as it is not the appropriate type of data structure. If you want to post a JSON array, the correct way to process it is to read the request body, as you have done.

    Also, note that because your index function does not take any arguments and therefore does not take advantage of the @service decorator's ability to map parameters from the HTTP request into function arguments, you could simplify your code by foregoing the @service decorator and accessing the index function more directly:

    def index():
        data = json.loads(request.body.read())
        return data
    

    Assuming index is in the default.py controller, you could post JSON to /yourapp/default/index.json (note the .json extension), and you will get back a JSON response.