1

I use this to go from URL params (received as bytes in the body of a HTTP request) to a JSON string:

import urllib.parse, json

def params_asbytes_to_json(b):
    s = b.decode()  # bytes to str
    x = urllib.parse.parse_qsl(s)  # parse_qsl
    d = dict(x)  # convert to dict
    return json.dumps(d)  # convert to string with json 

print(params_asbytes_to_json(b'a=b&c=d'))  # {"a": "b", "c": "d"}

It works, but as it involes many encoding/decoding steps, I suspect it to be inefficient (if my server receives many requests).

Is there shorter/faster to go from bytes a=b&c=d to the JSON string {"a": "b", "c": "d"}?


PS: this data is sent from a HTML <form> like this:

<form action='/do' method='post'>
<input type='text' name='a' value='b'>
<input type='submit'>
</form>

and, for other pages, via (Vanilla) Javascript:

var xhr = new XMLHttpRequest();
xhr.open("POST", "/do");
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
var fd = new FormData(document.querySelector('form'));
fd.append('dt', +new Date());
xhr.send(new URLSearchParams(fd).toString());

PS: I have already read How to send a JSON object using html form data, but I need my website to work even if Javascript is disabled, so I can't 100% rely on XMLHttpRequest + JSON.serialize : it needs to work with just a <form> submit. As far as I know, a standard HTML form can't post directly as JSON, is that right?

Basj
  • 41,386
  • 99
  • 383
  • 673
  • I think its fine. Moving from one encoding to another has its costs. – tdelaney Apr 13 '20 at 16:39
  • @tdelaney Do you think there's a way to ask the original `
    ` to directly send as JSON? it would be far better, and less heavy for my server.
    – Basj Apr 13 '20 at 16:56
  • I don't think this is heavy on your server compared to everything else going on to implement a web server, but.... there is an `application/json` proposal for forms https://www.w3.org/TR/html-json-forms/. I don't know if its widely adopted. – tdelaney Apr 13 '20 at 17:12
  • @tdelaney this looks like a good proposal, let's hope it'll be supported! I searched with caniuse.com but I didn't find it, also I tested on Chrome, and it does not seem to work, as of April 2020. – Basj Apr 13 '20 at 18:51

1 Answers1

0

You can save one step by using urllib.parse.parse_qs() instead of urllib.parse.parse_qsl(). It returns a dictionary rather than a list, so you don't need the dict() step.

However, as pointed out in the comments, this will set the dictionary values to lists, rather than individual strings, to allow for duplicate keys in the query string. Your version simply discards the duplicates. So either do it your way, or extract the elements out of the lists in the dictionary before calling json.dumps().

You also don't need to use b.decode(). The urllib.parse functions accept a bytes object and will decode it.

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • IIRC, `parse_qs` would give {'a': ['b']} instead of {'a': 'b'}. – Basj Apr 13 '20 at 16:35
  • @Basj - good point. `parse_qs` accounts for multiple values for a paramter by making a list. That's likely better for OP in the first place, IMHO. – tdelaney Apr 13 '20 at 16:37
  • The problem with skipping decode is that the dict will be `bytes` and json will raise an error. – tdelaney Apr 13 '20 at 16:44
  • Perhaps it should be `b.decode("ascii")` though instead of just decoding to whatever your local default is. – tdelaney Apr 13 '20 at 16:45