The time might come when we add some JSON specific command line options
curl.se
curl.se
For example, here's a JSONy POST request with cURL:
curl -s -H "Content-Type: application/json" -X POST https://api.ctl.io/v2/authentication/login --data '{"username":"YOUR.USERNAME","password":"YOUR.PASSWORD"}'
Here's that same request with HTTPie:
http POST https://api.ctl.io/v2/authentication/login username=YOUR.USERNAME password=YOUR.PASSWORD
I alias it to http so it's memorable and the commands make more sense though.
Happy to see that xh supports HTTP/2 out of the box.
curl -XPOST --jp username=YOUR.USERNAME password=YOUR.PASSWORD https://api.ctl.io/v2/authentication/login
Which isn't too far from your desired outcome (notably, without relying on argument position for meaning). Although, I guess the argument that "curl is already installed on almost every server", sorta gets moot because I imagine it will take a while for most distros to move to the latest curl that will support --json/--jpMost dependencies get updated much more quickly. It wouldn't even shock me if this change got picked up mid-cycle.
Curl is a command line tool. As long as they only add new functionality, there is very little that prevents an upgrade.
There's a happy medium, and we're not in it.
$ jo foo=$(jo bar=qux) | http example.com
Piped JSON automatically uses POSTYou might be in luck since Nested JSON support is going to be star feature of our upcoming release. Here is a sneak peek:
$ http --offline --print=B pie.dev/post \ search[type]=client \ search[stars]:=50000 \ search[platforms][]=Web \ search[platforms][]=Desktop \ search[platforms][]=Mobile \ search[platforms][]=CLI
{ "search": { "platforms": [ "Web", "Desktop", "Mobile", "CLI" ], "stars": 50000, "type": "client" } }
We are rolling a brand-new mini-language that integrates really well with the existing request building syntax, but also features stuff like JSON type-safety and amazing error messages for basic syntax errors.
POST https://api.ctl.io/v2/authentication/login
{
"username": "YOUR.USERNAME",
"password": "YOUR.PASSWORD"
}
and it will send the same POST request with a json body: hurl post.hurl
You can add asserts on the response too: POST https://api.ctl.io/v2/authentication/login
{
"username": "YOUR.USERNAME",
"password": "YOUR.PASSWORD"
}
HTTP/1.0 200
[Asserts]
jsonpath "$.status" == "LOGGED"
Under the hood, we use libcurl and a Rust binding. The HTTP engine is curl because curl is awesome!Awesome name.
curl -d @post.json http://example.comI can hand another dev a curl statement without having to worry if they have the requisite software to reproduce the call.
Also Postman, Insomnia, Sentry, & Swagger all support ability export to curl.
curl -H'Content-Type: application/json' -d @- << 'JSON'
Invoke with post_json <url>
Then just paste or write JSON without caring about quotes etc, finish the heredoc, done. -s: if HTTPie disables progress bar by default, that's just a different design choice, the advantage is in the eye of the beholder
--data '{"user...: likewise, HTTPie's default to JSON is a design choice. I wouldn't say the default is superior
-H "Content-Type...: likewise, if HTTPie adds this header by default, thats just getting in my way when I don't want that header
-X POST: you don't need to specify that in cURL if using --dataYou can use the `name==value` query parameter syntax [0]:
$ http --offline pie.dev/get AAA==AAA AAA==BBB
GET /get?AAA=AAA&AAA=BBB HTTP/1.1
Or simply: $ http --offline 'pie.dev/get?AAA=AAA&AAA=BBB'
GET /get?AAA=AAA&AAA=BBB HTTP/1.1
[0]https://httpie.io/docs/cli/querystring-parameters post api.ctl.io/v2/authentication/login < 1.txt|openssl s_client -connect api.ctl.io:443 -ign_eof
post is a 702-character shell script. printf is a built-in. #!/bin/sh
(
y=Connection;n=0;while read x;do
x1=${1#*//};x2=${x1%%/*};x3=${x1#*/};
x=$(printf "%s" "${x%%:*}:";echo "${x#*:}");
if test x"${x3}" = x"${x2}";then x3="";fi;
printf "%s\r\n%s\r\n%s\r\n%s\r\n" \
"POST /${x3} HTTP/1.1" \
"Host: ${x2}" \
"Content-Type: application/json" \
"Content-Length: ${#x}";
if [ $n -gt 1 ];then
printf "%s\r\n\r\n%s\r\n" "$y: keep-alive" "$x";else
printf "%s\r\n\r\n%s\r\n" "$y: close" "$x";fi;
export n=$((n+1));
done;
if [ $n -gt 1 ];then
printf "%s\r\n%s\r\n%s\r\n" \
"GET /robots.txt HTTP/1.0" \
"Host: ${x2}" \
"$y: close";fi;
)
Data to be posted may be stored in a one-line file named "1.txt" cat > 1.txt
{ "username": "YOUR.USERNAME", "password": "YOUR.PASSWORD" }
^D
Examine the request post api.ctl.io/v2/authentication/login < 1.txt
POST the request post api.ctl.io/v2/authentication/login < 1.txt|nc -vvn 127.1 80
Alternatively, data to be posted can be read from stdin echo '{ "username": "YOUR.USERNAME", "password": "YOUR.PASSWORD" }' \
|post api.ctl.io/v2/authentication/login \
|nc -vvn 127.1 80
If the data to be posted is JSON formatted as multiple lines such as {
"username": "YOUR.USERNAME",
"password": "YOUR.PASSWORD"
}
then something like (tr -d '\12' < 1.txt;echo) \
|post api.ctl.io/v2/authentication/login \
|nc -vvn 127.1 80
For TLS we use proxy listening on 127.0.0.1, e.g., stunnel, sslsplit, haproxy, etc. This way we only ever have to type a single address and port, i.e., 127.1 80, or short alias for the hostname, e.g., echo 127.0.0.1 p >> /etc/hosts. cat > 1.cfg
pid=/tmp/1.pid
[ x ]
accept=127.0.0.1:80
client=yes
connect=64.15.182.200:443
options=NO_TICKET
options=NO_RENEGOTIATION
renegotiation=no
sni=
sslVersion=TLSv1.3
^D
stunnel 1.cfg post api.ctl.io/v2/authentication/login < 1.txt|openssl s_client -connect api.ctl.io:443
post is a 702-character shell script. printf is a built-in. #!/bin/sh
(
y=Connection;n=0;while read x;do
x1=${1#*//};x2=${x1%%/*};x3=${x1#*/};
x=$(printf "%s" "${x%%:*}:";echo "${x#*:}");
if test x"${x3}" = x"${x2}";then x3="";fi;
printf "%s\r\n%s\r\n%s\r\n%s\r\n" \
"POST /${x3} HTTP/1.1" \
"Host: ${x2}" \
"Content-Type: application/json" \
"Content-Length: ${#x}";
if [ $n -gt 1 ];then
printf "%s\r\n\r\n%s\r\n" "$y: keep-alive" "$x";else
printf "%s\r\n\r\n%s\r\n" "$y: close" "$x";fi;
export n=$((n+1));
done;
if [ $n -gt 1 ];then
printf "%s\r\n%s\r\n%s\r\n" \
"GET /robots.txt HTTP/1.0" \
"Host: ${x2}" \
"$y: close";fi;
)
Data to be posted may be stored in a one-line file named "1.txt" cat > 1.txt
{ "username": "YOUR.USERNAME", "password": "YOUR.PASSWORD" }
^D
Examine the request post api.ctl.io/v2/authentication/login < 1.txt
POST the request post api.ctl.io/v2/authentication/login < 1.txt|nc -vvn 127.1 80
Alternatively, data to be posted can be read from stdin echo '{ "username": "YOUR.USERNAME", "password": "YOUR.PASSWORD" }' \
|post api.ctl.io/v2/authentication/login \
|nc -vvn 127.1 80
If the data to be posed is JSON formatted as multiple lines such as {
"username": "YOUR.USERNAME",
"password": "YOUR.PASSWORD"
}
then (tr -d '\12' < 1.txt;echo) \
|post api.ctl.io/v2/authentication/login \
|nc -vvn 127.1 80
For TLS we use proxy listening on 127.0.0.1, e.g., stunnel, sslsplit, haproxy, etc. cat > 1.cfg
pid=/tmp/1.pid
[ x ]
accept=127.0.0.1:80
client=yes
connect=64.15.182.200:443
options=NO_TICKET
options=NO_RENEGOTIATION
renegotiation=no
sni=
sslVersion=TLSv1.3
^D
stunnel 1.cfghttps POST api.ctl.io/v2/authentication/login username=YOUR.USERNAME password=YOUR.PASSWORD
-d '{ "foo": "bar", "zed": "yow" }'
The proposed --jp flag seems worse to me in every way.
(Note I do like the --json as just syntactic sugar for -H "Accept: application/json" -d <jsonBody>)
Yeah, actually it is. It's immediately intuitive to me. It makes string interpolation way easier. Quick, how do you do -d {object} and pass in environment variables with correct string escaping? Do you start with single quote or double quote? Where do I put backslashes? Bash vs zsh compatibility? Plus you have to make sure all the slashes, quotes, brackets and braces match.
Vs
--jp foo=$FOO --jp bar="baz-${BAR:-default-bar}" --jp date="\"$(date)\""
(I'm iffy on the last one, but it's WAY easier than trying to build that into an object)
If it's going to properly crib `jo` syntax, you can just do `date="$(date)"` - no need for the second set of quotes (and, indeed, they'll mess it up.)
> jo date="$(date)"
{"date":"Thu 20 Jan 2022 20:59:16 GMT"}
> jo date="\"$(date)\""
{"date":"\"Thu 20 Jan 2022 21:00:28 GMT\""}The notation seems to be similar to HAML<>HTML. It's not JSON, and it doesn't make any sense other than really short ad hoc queries. It's just confusing and only solves a problem that a tiiiny amount of people have (regualar ad hoc json queries, where people are too lazy to actually write out json).
Otherwise, why not do the same for XML, css. Or heck.. why not simply support HAML as well
It's better to have such functionality extracted in some other tool and just pipe it
bash$ curl ... --jp "foo=$foo" ...
This has zero shell-quoting, expansion, escaping, or separator issues. Whatever's in environment variable `foo` will be sent to the server as a single value associated with key `foo`, whether it's a zero-length empty string, or full of backslashes or spaces or newlines or whatever.There are fancier ways to accept multi-arg, but they all have weaknesses, and this matches the way curl handles -H arguments already today (one per header, stack them if you want many), so I think it's a sound way to handle CLI arguments.
(I don't have any specific views on whether this is how curl should do JSON or not, but I recognized the CLI safety mechanism immediately.)
It is so weird that command-f on that wiki doesn't show a single content-type header
$ jp foo=bar zed=yow
{ "foo": "bar", "zed": "yow" }
$ jp foo=bar zed=yow | curl --json - https://example.com/destination
...result here... $ jo query="six times nine" answer=42 | curl --json -
Sending JSON...
{"query":"six times nine","answer":42}Uh oh, this looks like it would have the problems of yaml. The data type changes based on the provided string.
For your development laptop you can install anything you want but more often than not you need to log into a EC2 instance, a Docker container you name it.
Curl is often pre installed or very easy to install. I know it's usually not an up to date version but as time goes by you will be able to rely on this feature on pretty much any machine.
> A not insignificant amount of people on stackoverflow etc have problems to send correct JSON with curl and to get the quoting done right, as json uses double-qoutes by itself and shells don't expand variables within single quotes etc.
It's about sanitized inputs.
Less sanitized and more correctly formatted. Writing literal JSON by hand at the CLI is not fun.
$ my_json="$(cat <<EOF
> {
> "foo": "bar",
> "baz": 42
> }
> EOF
> )"
$ echo "$my_json"
{
"foo": "bar",
"baz": 42
}
But I definitely didn't remember how to handle the closing paren and closing quote correctly, and I had to google for an example just now. So I'm not allowed to say this is easy to remember :) foo="$(cat <<EOF)"
whatever
EOF
This is left undefined in the POSIX standard and bourne shells don't allow it. data=$(jq -n \
--arg title 'what"ever' \
--arg endpoint 'foo"bar' \
'{
"title": $title,
"endpoint": $endpoint,
"enabled": true
}')I use bash variables inside JSON with curl all the time, which leads to string escape screw ups. I know there are alternatives that make testing REST + JSON easier, but since our software uses libcurl in production I prefer to test with curl to keep things consistent.
``` echo { "my": "json" } | http post localhost/endpoint ```
I wish the curl command was split such that different protocols had different commands. I REALLY don't want to see a list of FTP specific command line options whenever I'm just trying to look up a lesser-used HTTP option.
That said, this is really a minor gripe compared to just how useful curl has been for me over the years.
OTOH, if you ignore using curl to GET resources to download, >90% of my curl usage is slinging json, and often involves interpolating strings and hence copy-pasting, so this feature would be immediately useful to me.
Curl is kind of the swiss army knife of the web so I don't think a long manpage is out of line.
--jp a=b --jp c=d --jp e=2 --jp f=false
Gives:
{ "a": "b", "c": "d", "e": 2, "f": false }
--jp map=europe --jp prime[]=13 --jp prime[]=17 --jp target[x]=-10 --jp target[y]=32
Gives:
{ "map": "europe", "prime": [ 13, 17 ], "target": { "x": -10, "y": 32 } }
While this is neat, I suppose, it seems like such a waste that the first one isn't given as:
--jp a=b,c=d,e=2,f=false
And the second as:
--jp map=europe --jp prime[]=13,17 --jp target[]=x:-10,y:32
...or similar. The repetition kind of bothers me.
var="foo,y=bar"
curl --jp "x=$var"
Then allowing comma-separated field=value pairs within a single --jp argument would cause non-obvious changing behavior curl --jp "x='$var'"
Gives: curl --jp "x='foo,y=bar'"
Assuming that is what you meant. Isn’t this normally how these things are handled in unix shells? That and escaping which probably doesn’t apply here.I don’t know what the best way to implement this would be, but the current proposal looks so weird for me that I’m either completely missing something or it’s wildly unnecessary.
bash$ foo=this\"test\"
bash$ echo "value of \$foo is: $foo"
value of $foo is: this"test"
If we're throwing away the ability to let the shell handle escaping properly then there's really no point to --jq at all versus just manually attempting to cobble together JSON directlyRelated to the second point, I really wish more people put more time into creating tools for their testers. Shell/Ruby/Python/Perl scripts that are custom-made for the specific service they're testing and provides better UI. So that instead of a sequence of curl invocations, logins, and error-prone copy-pasting, people could just:
test-my-service --user j.doe:hunter2 --api comments/create --param body="hello world"Needless to say, the industry found powerful tools like AWK (and SystemD) more useful than rigid dogmas.
Was "rigid dogmas" in reference to the Unix Philosophy? I haven't ever seen it described that way.
It is the antithesis of the Unix Philosophy. Always has been. And that's okay.
Utility #1: 580-character shell script to generate HTTP (NB. printf is a built-in)
Utility #2: TCP client to send HTTP, e.g., netcat
Utility #3: (Optional) TLS proxy if encryption desired, e.g., stunnel^1
1. For more convenience use a proxy that performs DNS name lookups. Alternatively, use TLS-enabled client, e.g., openssl s_client, etc.
Advantages over curl and similar programs: HTTP/1.1 pipelining
For the purpose of an example, the shell script will be called "post". To demonstrate pipelining POST requests, we can send multiple requests to DuckDuckGo over a single TCP connection. TLS proxy is listening on 127.0.0.1:80.
#! /bin/sh
(
y=Connection;n=0;while read x;do
x1=${1#*//};x2=${x1%%/*};x3=${x1#*/};
if test x$x3 = x$x2;then x3="";fi;
x=$(printf "%s" "${x%%=*}=";echo "${x#*=}");
printf "%s\r\n%s\r\n%s\r\n%s\r\n" \
"POST /${x3} HTTP/1.1" \
"Host: ${x2}" \
"Content-Type: application/x-www-form-urlencoded" \
"Content-Length: ${#x}";
if [ $n -gt 1 ];then
printf "%s\r\n\r\n%s\r\n" "$y: keep-alive" "$x";else
printf "%s\r\n\r\n%s\r\n" "$y: close" "$x";fi;
export n=$((n+1));
done;
if [ $n -gt 1 ];then
printf "%s\r\n%s\r\n%s\r\n" \
"GET /robots.txt HTTP/1.0" \
"Host: ${x2}" \
"$y: close";fi;
)
Put the queries in a file cat > 1.txt
q=one
q=two
q=three
^D
Send the queries post https://lite.duckduckgo.com/lite < 1.txt|nc -vvn 127.1 80
Send the queries, save the result, then read the result echo "<base href=https://lite.duckduckgo.com />" > 1.htm
post https://lite.duckduckgo.com/lite < 1.txt|nc -vvn 127.1 80 >> 1.htm
firefox ./1.htm
links -no-connect ./1.htm
Based on personal experience as an end user, I find that using separate utilities is faster and more flexible than curl or similar program mentioned in this thread. For me, 1. storage space for programs, e.g. large scripting language interpreters and/or other large binaries, is in short supply and 2. HTTP/1.1 pipelining is a must-have. Using separate, small utilities 1. conserves space and 2. lets me do pipelining easily. I write many single purpose utilties for own use, including one that replaces the "post" shell script in this comment.One alternative would be to provide escaping more directly like this:
curl --json '{
"map": %s,
"prime": [
%i,
%i
],
"target": {
"x": %i,
"y": %i
}
}' "$continent" "$p1" "$p2" "$x" "$y" https://example.com
And then curl would do the substitution with the appropriate type-specific escaping for each variable. This has a few nice properties:1. What's on the command line resembles what's actually going to be sent.
2. Curl doesn't actually need to parse (nor validate) the JSON, or to create a tree representation of the data within itself. %s is invalid JSON anyway, so you can do a string substitution - all you need to keep track of are matching quotes (including escape sequences).
I've used a printf style format string here, which could be expanded for extra convenience. For example the Python-style `%(env_var)s` sequences could be used which could expand environment variables directly. Or something could be added for convenient handling of bash arrays.
Because cURL is so ubiquitous, whatever Daniel implements may become the de facto standard.
- The `--json` option only adds a content-type header, it doesn't alter the transmitted data at all.
- The `--jp` option has a bespoke format that's not part of the JSON spec, and which doesn't actually depend on a specific data model, it's just string manipulation.
Also, —jp is actually generating a JSON.
The problem is not with the JSON spec, the problem is when you are converting from one data model to another. Any program which claims to perfectly round-trip the JSON data-model should support arbitrarily long numbers, there's no ambiguity in the spec about that.
If you are only parsing JSON as a means to encode your own data-model, then there's no obligation to support arbitrary precision, but users should not expect to be able to round-trip arbitrary JSON data.
AFAICT, `--jp` doesn't do anything which would affect the length of supported numbers, even though it's generating JSON.
> JSON is agnostic about the semantics of numbers. […] JSON instead offers only the representation of numbers that humans use: a sequence of digits. […] That is enough to allow interchange.
But can you encode/decode an arbitrary integer or a float? Probably not!
* Float values like Infinity or NaN cannot be represented.
* JSON doesn't have separate representation for ints and floats. If an implementation decodes an integer value as a float, this might lose precision.
* JSON doesn't impose any size limits. A JSON number could validly describe a 1000-bit integer, but no reasonable implementation would be able to decode this.
The result is that sane programs – that don't want to be at the mercy of whatever JSON implementation processes the document – might encode large integers as strings. In particular, integers beyond JavaScript's Number.MAX_SAFE_INTEGER (2^53 - 1) should be considered unsafe in a JSON document.
Another result is that no real-world JSON representation can round-trip “correctly”: instead of treating numbers as “a sequence of digits” they might convert them to a float64, in which case a JSON → data model → JSON roundtrip might result in a different document. I would consider that to be a problem due to underspecification.
Jason.org requires white space for empty arrays and objects while RFC 8259 does not (and I often see [] and {} in the wild).
A lot of packages de fact break the spec in other ways, such as ppl blatting python maps out rather than converting them to JSON so that the keys are quoted as ‘foo’ rather than “foo”. I’ve complained about this when trying to parse the stuff only to receive the response “it works for me so you must have a bug” from the pythonistas. This has happened in multiple projects.
If it’s too tough to integrate with other tools like jq, maybe that could provide for a better outcome.
Something that would be helpful is for cURL, HTTPie, Postman, Fiddler, etc to standardize on a request/response pair format such as Chrome's HAR. There are some tools in NPM and the below HAR to cURL too, so I think native HAR support would be more helpful than a JSON builder.
As a user I would not expect curl to have json functionality.
And as a developer I would prefer to have one codebase deal with http and another one with json.
curl() {
args=()
for arg in "$@"; do
case $arg in
--json) args+=("-H" "Content-Type: application/json") ;;
*) args+=("$arg") ;;
esac
done
command curl "${args[@]}"
}If it wasn't for this kind of stuff, there probably wouldn't be as many jobs in IT as there are.
... interacting with APIs using cURL?
> I don’t find curl —jp a=b to be better than directly sending a payload on a HTTP resource
Getting JSON syntax right, error free, by hand, in a terminal, is not easy. The current equivalent of an eventual `curl --jp a=b` is
curl -s -H "Content-Type: application/json" --data '{"a":"b"}'
that's a lot of opportunities for getting it wrong.you are right and I wonder why shells haven't done anything to address this. Fish might, actually. colorization isn't really useful in aiding comprehension, but colorization is good at giving an indicator that there is a parse error somewhere.
NAME=taterman
EMAIL=sweettaterhater@taterman.com
curl --jp "user=$NAME" --jp "email=$EMAIL" http://getdemtaters.com
vs curl -d "{\"user:\"$NAME\",\"email\":\"$EMAIL\"}" http://getdemtaters.com
Even adding jq to requirements doesn't make it that much better: jq -n --arg name "$NAME" --arg email "$EMAIL" '{ "user": $name, "email": $email }' | curl -d @-Often times the JSON being sent down is complex, I can't imagine anyone wanting to basically rewrite it into something else for anything other than 2 field JSON objects
curl --insecure --cookie test_cookie='{"test":"bob"}' https://localhost:8081/
Host: localhost:8081
Accept: /
Cookie: test_cookie={"test":"bob"}
User-Agent: curl/7.74.0
curl --insecure --cookie 'test_cookie={"test":"bob"};test_cookie2={"test2":"bob"}' https://localhost:8081/
https://github.com/curl/curl/wiki/JSON
Wiki is a weird format to use for a proposal.
But aren't there also several command line utilities which already support JSON.
Why cram new stuff into such an industry standard tool?
There are command line utilities which consume, query, or format json.
But aside from e.g. httpie (which is essentially a competitor to Curl), which "several command-line utilities" make authoring JSON easy and convenient?
Because if you check point (3), the link, and the paragraph before it, this is entirely about sending valid JSON (ideally with the correct headers).
In fact the second section of the link in question literally states:
> # JSON response
> Not particular handling. Pipe output to jq or similar.
Infact you can run postman on the command line
https://learning.postman.com/docs/running-collections/using-...
Or you can write your own script in Python to do this.
I really don't like adding new functionality to standardized tools. It's just risky.
Make an extension, call it CURLson, but don't cram it into curl.
There are two standards for selecting an element in a document. XPath and, ugh, JQuery. JQuery is easy for newbies. XPath is the “real solution”.
JQ uses neither of these. Why? Who the hell knows.
(not serious)