the deal is basically that if you return a dict, web2py is compelled to pass it to the corresponding view, which in turns in most of cases returns html.
There's nothing wrong in returning a dict and then having a generic "json" view that serializes that dict, and as a matter of fact, generic.json does just that.
But if you return a string, you skip the "render the view" part.
On the parsing side: if you are looking for json, you should also return the correct content-type if you want libraries (e.g. jQuery) to parse for themselves. Either you force it with response.headers['Content-Type'] = 'application/json' or - my personal preference - you just ask for /a/c/f.json instead of /a/c/f . the correct content type will be set by web2py.