Here's the flask code:
from flask import Flask, request
import json
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def refresh():
    params = {
        'thing1': request.values.get('thing1'),
        'thing2': request.values.get('thing2')
    }
    return json.dumps(params)
Here's the cURL:
$ curl -XGET 'http://127.0.0.1:5000/?thing1=1' -d '{"thing2":2}'                                              
> {"thing1": "1", "thing2": null}
$ curl -XGET 'http://127.0.0.1:5000/?thing1=1' -d '{"thing2":2}'                                              
> {"thing1": "1", "thing2": null}
The docs seem VERY clear that this should be working:
form
A MultiDict with the parsed form data from POST or PUT requests. Please keep in mind that file uploads will not end up here, but instead in the files attribute.
args
A MultiDict with the parsed contents of the query string. (The part in the URL after the question mark).
values
A CombinedMultiDict with the contents of both form and args.
Any ideas what I'm doing wrong?
Update: Trying suggestions from one of the answers, swapping out the return line:
Using return json.dumps(json.load(request.body.decode("utf-8") )) generates the error AttributeError: 'Request' object has no attribute 'body'
Using return json.dumps(json.load(request.json)) generates the error AttributeError: 'NoneType' object has no attribute 'read'
Using POST with the origin code appears to have no effect:
$ curl -XPOST 'http://127.0.0.1:5000/?thing1=1' -d '{"thing2":2}'
{"thing1": "1", "thing2": null}
Setting the content type and using POST with the original code also has no apparent effect:
$ curl -XPOST -H "Content-Type: application/json" 'http://127.0.0.1:5000/?thing1=1' -d '{"thing2":2}' 
{"thing1": "1", "thing2": null}
although I went and verified that the content-type was then correctly set:
...
print(request.headers)
...
Host: 127.0.0.1:5000
User-Agent: curl/7.54.0
Accept: */*
Content-Type: application/json
Content-Length: 12
                You're inadvertently sending the wrong Content Type.
By default, curl's -d flag will send POST data with content-type application/x-www-form-urlencoded. Since you're not sending data in the format that it's expecting (key=value), it's dropping the data altogether. For JSON data, you'll need to send the HTTP request with the content-type set to application/json like so:
curl -XPOST -H "Content-Type: application/json" 'http://127.0.0.1:5000/?thing1=1' -d '{"thing2":2}'
Also, flask's request.form field only contains POST form data, not other content types. You can access the raw POST request body with request.data or, more convenienty, the parsed JSON data using request.get_json.
Below is a fixed version of your example:
from flask import Flask, request
import json
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def refresh():
    params = {
        'thing1': request.values.get('thing1'),
        'thing2': request.get_json().get('thing2')
    }
    return json.dumps(params)
app.run()
UPDATE: I misspoke earlier - request.body should actually be request.data. Also as it turns out request.json is deprecated and request.get_json should be used now instead. The original post has been updated.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With