Parsing JSON at the CLI: A Practical Introduction to jq and more
sequoia.makes.software
sequoia.makes.software
The first example:
ConvertFrom-Json $USERX | ConvertTo-Json | Set-Clipboard
Getting properties by name: ConvertFrom-Json $ORDER | select order*I've completely rid myself of GNU core utils with just pwsh.
I completely agree with the parent that PowerShell blows the existing shell options out of the water. Having pipelines with objects rather than just text makes everything so much easier. Instead of spending an hour futzing with awk or regex or applicatioons like jq to parse values from command results you can just access what you want directly and get on with your work.
1. https://docs.microsoft.com/en-us/powershell/scripting/instal...
For instance on my system:
Get-ChildItem -> gci or ls
Select-String -> grepNice list of aliases though.
Three commands that are useful to memorize, though, (particularly if you're having trouble remembering names) are Get-Command, Get-Alias (also with -Definition), and Get-Member. Get-Command gets you info about a command, like if it's an alias or not, and the path if it's a unix command. Get-Alias shows you all the active aliases, and Get-Alias -Definition shows you the active aliases for a given command. Get-Member shows you all the members of an object which can help you with Select-Object and Where-Object and so on.
grep -rl $pattern | $othercommand
So far I've got this (deliberately avoiding aliases for this example) in the above grep's command's stead: Get-ChildItem -Recurse -Path * | Select-String $pattern
But I just cannot fathom how to break from the table format to pass the lines to $othercommand.How does one pass a pwsh list to a single command? Using %{}/For-Each {} wouldn't work because it would invoke $othercommand for each line.
| format-list *
to investigate it, and you'll see a bunch of members. In this case if what you want is just the matched string, you want to expand the `Matches` property of the object, then expand the value property of each of those. In this case the term 'expand' is key: if you just use `select` it'll return you the property, name and all. Use `-expand` to return the plain array. So something like: Get-ChildItem -Recurse -Path * | Select-String $pattern | select -expand matches | select -expand value
which you can then use how you like. An alternate form is (Get-ChildItem -Recurse -Path * | Select-String $pattern).Matches.Value
because the . notation for member access works on all items in an array (Matches in this case). Get-ChildItem -Recurse -File | Select-String "$pattern" -List -SimpleMatch -CaseSensitive | Select-Object -ExpandProperty path
You will want to only search in files for this to not ouput some lines of InputStream if there is a match in the actual path.
If $pattern is an actual regex you will want to drop SimpleMatch.
In case you want some proper powershell objects you might want to pipe this into Get-Item.If you do write such a post make sure to let me know so I can link it from mine! You could write a response "here's how I'd do all the stuff in that post in Powershell with no extra tools" that would be very cool :)
Edit: Yes, it does.
'{ "a": { "b": { "c": { "d": { "e": 5 } } } } }' | ConvertFrom-Json | ConvertTo-Json
{
"a": {
"b": {
"c": "@{d=}"
}
}
}
So you have to remember to set `-Depth 100` for every invocation of `ConvertTo-Json`. You also can't set it higher because it has a hard-coded limit of 100, so hopefully you never deal with JSON that has more nesting than that.Yeesh. I like PS, but this one commandlet's design has always baffled me.
> echo '{ "a": { "b": { "c": { "d": { "e": 5 } } } } }' | from json | to toml
[a.b.c.d]
e = 5 gci | select -first 1 | ConvertTo-Json -Depth 2
gci | select -first 1 | ConvertTo-Json -Depth 3
gci | select -first 1 | ConvertTo-Json -Depth 4
one at a time.Or leave it as it is and expect the user to map the complex values into hashtables. Eg in your example, make the user pipe the objects through `%{ @{ Name = $_.Name; Mode = $_.Mode; } }` first.
In any case, I don't know about other PS users, but all my command-lines that ended with ConvertTo-Json either started with ConvertFrom-Json (transforming an existing JSON file), or started with HashTables (building a JSON file from scratch) or a mix of the two. Therefore I always wanted everything to be serialized, and the `-Depth` parameter was always a nuisance.
EDIT: I see your point now, thank you for explaining
It can be something as trivial as "PSHashTable is allowed to serialize without limit. Every other type is limited to N levels, unless the user sets `$SERIALIZATION_LIMIT[System.Type]` to some other value." Or make the `-Depth` parameter a `Dictionary<System.Type, int>`.
By that logic they should also add -MaxArrayLength to make the parser stop parsing long JSON arrays, -MaxProperties to make it only parse object properties up to a limit, -MaxStringLength to only make it parser strings up to a certain length...
It's completely pointless.
From-JSON wouldn’t have this concern, but to-JSON would
>Uncaught TypeError: cyclic object value
If browser JS engines can detect circular references, the PS serializer can too.
It sounds like whoever implemented this just didn't know what they were doing.
Earler today I was looking at some deeply nested structures which had leaf nodes with a field named "fileURL", and values that were mostly "https://" but some were "http://". I needed to see how many, etc.
cat file.json | gron | grep fileURL | grep http | grep -v https
... and presto, I had only four such nodes.
Would've been a ton more work to get there with just jq.
<file.json jq '.. | .fileURL? | select(startswith("http://"))' -r
... would've done the job?Or, if you can't remember `startswith`:
<file.json jq '.. | .fileURL?' -r | grep '^http://'Right, the ".." part was hard to remember because it's something like:
{
"entries": [
{
"fields": {
"fileURL": {
"en-US": "https://..."
},
...
},
...
},
...
]
}
and as you can see the field fileURL is actually an object with another field en-US (with a hyphen) so the jq becomes something like this:<file.json jq -r '.entries[] | .fields | select(.fileURL) | .fileURL["en-US"] | select(startswith("http://"))'
And later I had to do the same for other fields that ended in URL (websiteURL, etc.)
Anyway, gron made it simpler to get a quick summary of what I needed because it represents every node in the tree as a separate line that has the path in it, which is perfect for grep.
I still use jq more than gron :-)
You misunderstand. The command I wrote is meant to be used as I wrote it. `..` is not a placeholder for you to replace.
https://stedolan.github.io/jq/manual/#RecursiveDescent%3a%2e...
>and as you can see the field fileURL is actually an object with another field en-US (with a hyphen) so the jq becomes something like this:
Sure, so then it's:
<file.json jq '.. | select(.fileURL?).fileURL["en-US"] | select(startswith("http://"))' -rThanks for explaining!
so a jq utility[] that spits out all the jq paths for a json files helps in both cases.
sqlite3 <<< "select json_extract(value, '$.Plan.Plans[0].Output') from json_each(readfile('explain.json'))"
Reference: https://www.sqlite.org/json1.htmlback in a day I've loaded a lot of data to postgresql and elastic search after preprocessing it with very simple but powerful chain of CSV parsers (I've used CSVfix), jq, sort, grep, etc.
My favorite tool in this area is `lnav` (https://lnav.org), a scriptable / chainable mini-ETL with embedded sqlite.
Oh and tiny correction (intended as helpful not nit-picking), the idiom is "back in the day" not "back in a day". :)
Some tools from the Node.js universe. `npm install $etc`. https://www.npmjs.com/package/ndjson-cli
https://jsonlines.org https://github.com/ndjson/ndjson.github.io/issues/1
It could also be done as a library of adapters. e.x. something like
j ls
Would run “ls”, parse the output, and re-output it as JSON lines.I found the most value in some of the additional tricks shown in the article (like the “pretty print JSON stored in the clipboard) over the introduction/tutorial aspect; there are hundreds of these “look, I figured out how to do a handful of interesting things in jq so let me share with you all” tutorials floating around and most of the basics are redundant among those by now.
If jq’s documentation had some basic tutorials it would probably be a better authoritative source for this information.
Until then, as said, thanks for preparing and sharing this!
https://gist.github.com/joshgel/12082d23a75feaab5d405db31981...
> So, let's group by and count:
> cat 1396-Ledner.json | jq '.entry[].resource.resourceType' | sort | uniq -c | sort -nr This gives me a list that looks like this:
This is so funny! We came up with some of the exact same combinations of tools (jq + sort, uniq, wc etc.). I mean, it makes sense so I shouldn't be surprised!
But I use a lot JQ, so it's super easy for me ;-)
[0]https://github.com/kellyjonbrazil/jello
[1]https://blog.kellybrazil.com/2020/03/25/jello-the-jq-alterna...
I think it would have been much better to do this via jq instead, for the sake of demonstration. This is, after all, an article on jq, not on promql...
It would look like this, for posterity:
jq '.data.result[].metric | select(.app == "toodle-app").task_name'
The reason I was putting the querying into promql where possible is that it is (I assume) more efficient to do that filtering on the server & only return matching values rather than returning "everything" & filtering locally. But your point stands, it's tangential here. Thanks for the feedback this will definitely improve the post!
Edit: updated!
Babashka[1] with Cheshire[2] aliased as json solves this to me, the code is longer but no need for googling anymore:
USERX='{"name":"duchess","city":"Toronto","orders":[{"id":"x","qty":10},{"id":"y","qty":15}]}'
echo $USERX | jq '.orders[]|select(.qty>10)'
{
"id": "y",
"qty": 15
}
echo $USERX | bb -i -o '(-> *input* first (json/decode true) (->> :orders (filter #(> (:qty %) 10))))'
{:id y, :qty 15}
[1]: https://github.com/borkdude/babashka
[2]: https://github.com/dakrone/cheshireExample:
189MB JSON
curl https://raw.githubusercontent.com/zemirco/sf-city-lots-json/master/citylots.json |jq
while that is running watch the system memory using vmstat, top, etc.For example, it requires the entire input to be in memory. It does not support streaming input.
That's assuming your structure is list-large, rather than tree-large. Adjacency lists can often bridge the gap.
describes a JSON query tool that can use an order of magnitude less memory than jq. Its query language (JSON Pointer) is much simpler, though.
This comment says it best I think, "On the other hand, if you only need 53 bits of your 64 bit numbers, and enjoy blowing CPU on ridiculously inefficient marshaling and unmarshaling steps, hey, [JSON parsing] is your funeral." - Source https://rachelbythebay.com/w/2019/07/21/reliability/
HTTP is so inefficient. All those newline-deliminated case-varying strings.
Logs are so inefficient. Error codes are so much shorter and precise.
Computers are here to make humans' lives easier. But it's not easier if we're forced to use tools that communicate in obscure ways we can't understand and later need translators for. If a human has to debug it, either it has to be translated into a human-language-like form, or it can just stay that way to begin with and save us the trouble of having to build translators into anything that outputs, extracts, transforms, or loads data.
JSON is actually a wonderful general-purpose data format. It's somewhat simple (compared to the alternatives), it's grokable, it's ubiquitous, and you can `cat` it and not destroy your terminal. But of course it shouldn't be used for everything. Actually one of the only things I find distasteful about JSON is the lack of a version indicator to allow extending it. But maybe this was its saving grace.
If "blowing CPU on JSON marshaling" is your big problem, boy, your business must be thriving.
ASN.1 FTW!!!!!
:)
Probably the most popular modern alternatives:
* https://en.wikipedia.org/wiki/CBOR (RFC 8949)
I usually mash together a 10-steps pipeline of jq, grep (-v), vim, xargs until I find the data I need. If I need a reliable processing pipeline, I can afford to google 10min for how to use jq in more details!
[0]https://github.com/kellyjonbrazil/jello
[1]https://blog.kellybrazil.com/2020/03/25/jello-the-jq-alterna...