23 ms·
The Pretty JSON Revolution
- enriquto 6y agoThis beautiful post is missing a last section called "Gron: do away with json altogether and print something actually readable". That would be a good punchline!
- peterohler 6y agoInteresting idea. Solves the issue of trying to find data in JSON fairly well. I'm a bit biased but I like using the oj with a JSONPath extraction (-x option) to do something similar but with the power of JSONPath.
- Macha 6y agoIf you're on a mac or Linux system, you likely have a JSON formatter already installed `python3 -m json.tool somefile.json` or `cat foo.json | python3 -m json.tool` will print it in "one line per node" format. 3.9 introduces a --sort-keys switch for sorted objects also.
- taeric 6y agoI would be a little nervous sorting the keys. I thought it was not too uncommon for parsers to treat them as an alist where order matters. I guess so long as it is a stable sort, no big deal?
- peterohler 6y agoIt is a stable sort since for almost every parser out there there can be no duplicate keys. It would be dangerous for an JSON parser to assume JSON object keys are in some specific order. It is certainly not something that can be counted on in golang, Ruby, or Python.
- Splognosticus 6y agoAlso in violation of the spec: > An object is an unordered collection of zero or more name/value pairs, where a name is a string and a value is a string, number, boolean, null, object, or array. https://tools.ietf.org/html/rfc7159#section-1 https://tools.ietf.org/html/rfc7159#section-1
- taeric 6y agoThe spec does call out that some parsers deal with this. And since it is javascript based, leniency is the norm. That is, you could rely on this and but be aware of it, is my point. Firefox, for example, will happily take an object with duplicates and report only the last one.
- Splognosticus 6y agoWell, by definition "unordered" means you can't count on any particular order. So while parsers may indeed preserve order, anything that relies on it is in violation of the standard. That said, I agree that being aware of this is important if you're emitting JSON. You'd think nobody would ever address a JSON object by its ordinal position, but programmers are lazy and worse, think they're clever. :)
- taeric 6y agoExactly that. I did not mean to disagree that it is somewhat wrong. Just feels dangerous, as behavior could change with no side channel warnings.
- quietbritishjim 6y ago> It is certainly not something that can be counted on in golang, Ruby, or Python. As of Python 3.6 (in theory not guaranteed until Python 3.7), key order is preserved when reading and writing. That's a consequence of the fact Python dictionaries can now remember the order of insertion. It's true that it doesn't support duplicate keys though (unless you pass in a different class to the object_pairs_hook parameter of loads() to replace its use of dict).
- igetspam 6y agoIf you have a parser that's looking at keys in a hash as sorted, you should change your parser. Lists sure but not keys.
- gmfawcett 6y agoYou're free to encode meaning in the order if you want, as the JSON spec explicitly punts on the issue: "The JSON syntax does not impose any restrictions on the strings used as names, does not require that name strings be unique, and does not assign any significance to the ordering of name/value pairs. These are all semantic considerations that may be defined by JSON processors or in specifications defining specific uses of JSON for data interchange." https://www.ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf https://www.ecma-international.org/wp-content/uploads/ECMA-4...
- Macha 6y agoPretty printing JSON is mostly for developer consumption, I'm not sure how the pretty printed JSON would end up being fed automatically to another system? (I have actually encountered a order-dependent JSON-subset parser before, but to my mind, that code is broken)
- peterohler 6y agoPretty JSON is still just JSON. It should work with any JSON parser. Of course if it is being fed from one system to another there is no need to make the JSON anything other than compact one line JSON. Pretty is for human consumption.
- jandrese 6y agoSome parsers will reject JSON that has whitespace around the elements. Pretty JSON is really only for human consumption.
- wayneftw 6y agoOutside of pretty printing - MySQL breaks your app if you depend on key order in an object because it alphabetically sorts object keys when you store it in a JSON column. JavaScript itself will also sort object keys if they are numeric, so `{a: "a", c: "c", b: "b", "1": 1};` will be transformed to `{1: 1, a: "a", c: "c", b: "b"}`.
- lucideer 6y agoIf you have an application that's dependent on JSON key order, then you're not passing it prettified JSON. The main apps I've seen that depend on JSON structure are for hashing, which would also be broken by whitespace / linebreak variances in pretty-printers.
- billpg 6y agoI wold rather pritty printing retain original order. Some JSON schemas I've seen put a version or class type at the top of every object and I wouldn't want that to be ordered elsewhere.
- igetspam 6y agoI did not know about sort keys. I'm adding that to my alias. Thank you.
- peterohler 6y agoOj supports a config file as well. .oj-config.sen. -help-config will describe it in more detail.
- foobarian 6y agojq is a very nice tool for JSON wrangling available to install on most distros. It also provides key sorting, which is great for diff-ing JSON.
- peterohler 6y agojq is a nice tool. oj is similar in many ways but different in others. jq has it's own proprietary query language while oj uses JSON path. The output options are also different with some overlap. Maybe jq will get a pretty output option after reading the article. :-)
- pcthrowaway 6y agojq output is "pretty" by default, just without the ability to customize the prettification (as far as I know). If you want non-"pretty" output you need to add the `-c/--compact` option
- foobarian 6y agojq output is also colorized on a tty output. This can be forced when piping to a pager e.g. `<json-producing command> | jq -S -C . | less -R`
- graton 6y ago> Maybe jq will get a pretty output option after reading the article. :-) In my experience jq already does pretty the output. Maybe I'm missing something in your comment.
- onetom 6y ago"pretty output" meaning the one called "Human Style" in the article, where multiple array elements or key-value pairs are compacted onto 1 line, IF they fit into the specified line length.
- kgilpin 6y agoI found JSON path to be so, so weak and limiting. Missing powerful axis traversals like xpath has, and also has very confusing semantics (the filter condition also changes the output???). I hoped to find jq as a gem/module/library but I was disappointed. After days of searching and trying different things, I honestly could not find any powerful library or API for traversing and searching JSON.
- augusto-moura 6y agoI prefer Nushell[1] for data processing, it's a full fledged shell but I rarely use it as a interactive shell, mostly as a scripting language and some one-offs oneliners. It supports CSV, JSON and other languages by default and provide the data a much nicer common interface Something like: open file.json | select colors | each { ^echo $it.hex } is much nicer than jq [1]: https://www.nushell.sh/ https://www.nushell.sh/
- jandrese 6y agoI use json_pp a lot. It's usually installed in the base OS. json_pp < somefile.json The only thing I don't like is that it doesn't process commandline arguments. You have to pipe the file in. It is also fairly strict, I've run into a number of malformed JSON files that it rejects but other parsers would accept. Naked TRUE/FALSE statements are one thing it hates that are super common, especially from places like Google.
- AzzieElbab 6y agoI expected a Mona Lisa as json by the end of this article
- peterohler 6y agoNow that would be cool! :-)
- flaie 6y agoHere you go: https://pastiebin.com/603528abd6813 https://pastiebin.com/603528abd6813
- AzzieElbab 6y agoJust look at that #smile
- benatkin 6y agoIn addition to outputting HTML, it could output JSON that could be rendered to HTML, the terminal, or JSX: https://github.com/wooorm/lowlight#projects https://github.com/wooorm/lowlight#projects
- chrisweekly 6y agooo cool, wooorm looks useful, thanks for the link
- benatkin 6y agoYou're not wrong, but wooorm is a person. Remark and Unified are some well-known projects that wooorm maintains. https://github.com/remarkjs/remark https://github.com/remarkjs/remark https://unifiedjs.com/ https://unifiedjs.com/
- chrisweekly 6y agohahaha (facepalm) I use remark, too. Sorry wooorm!
- taeric 6y agoI would prefer one that aligned the like named keys, if it fits in screen. Makes it dead easy to scan the values. That said, it is just another pun on the text as art thing. In that it doesn't really scale, and you are going to upset someone by not having a codified tool for automatically doing this. (I don't recall seeing align-regex in any popular tool.)
- peterohler 6y agoCan you explain what you mean by "like named"? The sorting helps a lot but I'm always interested in additional features.
- taeric 6y agoThe current top comment is what I meant. On my phone, so couldn't put an example easily.
- dan-robertson 6y agoI assume they mean vertical alignment like: [ { foo: a bar: 123.45 } { foo: abc bar: 6.7 } ]
- peterohler 6y agoAnother post said something similar. An issue was added to add the feature. I think it is a good one to add.
- thechao 6y agoAs a joke I developed a format called "KVIN" which is like GRON but "context-sensitive": foo.bar.baz = 10 .biz = 12 // foo.bar.biz ..boz.baz = 31 // foo.boz.baz etc. It basically combines really brittle context-sensitive grammar production with complete lack of greppability.
- vicpara 6y agoJSON is mostly for machines not people. When needed, developers format their json in their code editor of choice or bash.
- peterohler 6y agoThere are a lot of people who store JSON in NoSQL databases. After fetching a JSON records you generally view the JSON or you do as a developer. That is where the tool is handy as you get get something like a FHIR record on a single page instead of crunched into a single line to expanded over multiple pages.
- slingnow 6y agoJSON is mostly for people and not machines in that it is meant to be easily readable and editable by humans. If you wanted a something for machines you would store your data in a compressed/binary format.
- Pxtl 6y agoWhich demonstrates that JSON is pointless. It's too ugly for humans (too many quotes, too many escape characters, and no comments) and too texty for machines.
- gpvos 6y agoTrue, and this is a nice tool to do so.
- Phrodo_00 6y ago> the conversion from SEN to JSON and the reverse is lossless How does SEN deal with numbers-encoded as string? is it something like .4 ? that's a bit confusing
- peterohler 6y agoSEN still uses quotes when necessary. For any sting that starts with a number, the sting is quoted. Note in the example the hex colors are in quotes since the `#` character is not valid unquoted token character.
- thechao 6y agoPlease: { "colors": [ { "color": "black", "hex": "#000", "rgb": [ 0, 0, 0 ] }, { "color": "red", "hex": "#f00", "rgb": [ 255, 0, 0 ] }, { "color": "yellow", "hex": "#ff0", "rgb": [ 255, 255, 0 ] }, { "color": "green", "hex": "#0f0", "rgb": [ 0, 255, 0 ] }, { "color": "cyan", "hex": "#0ff", "rgb": [ 0, 255, 255 ] }, { "color": "blue", "hex": "#00f", "rgb": [ 0, 0, 255 ] }, { "color": "magenta", "hex": "#f0f", "rgb": [ 255, 0, 255 ] }, { "color": "white", "hex": "#fff", "rgb": [ 255, 255, 255 ] } ] }
- peterohler 6y agoAn issue has been added to the repo. It is on the list now. Great idea, thanks. https://github.com/ohler55/ojg/issues/35 https://github.com/ohler55/ojg/issues/35
- falcolas 6y agoOpinionated Opinion: That's an incredibly XML-ified version of a color table. I can clearly see the tags now. Can't just do a look up of a color color, instead I would have to iterate over the members or store it in a different data structure. Why even use JSON? Blech.
- j-krieger 6y agoJust because the data is structured this way in the example, doesn't mean it's not possible. What's hindering you from defining a class property for each color? "colors": { "red":{"rgb":"fff"}", ... }
- ehnto 6y ago> Those two parameters are specified as a float where the whole number part is the edge and the fractional part or the number of 10ths is the maximum depth on a single line. Is that a convention I'm not aware of? Seems a little obtuse and unnecessary, why not just accept two arguments? One less arbitrary usage detail to remember.
- peterohler 6y agoI keep the `-p` (pretty) option as a single option. Not a convention at all. I toyed with 80x3 and 80:3 but ended up with 80.3. You are right though, it probably make sense to support two options as well to avoid the unusual convention. Maybe a `-edge` and `-max-depth` options in addition. Issue created: https://github.com/ohler55/ojg/issues/36 https://github.com/ohler55/ojg/issues/36
- ehnto 6y agoThanks for the reply, the combined parameter was borne out of your real world usage so I'm glad it sounds like you'll keep it in. Hope I didn't come off cynical, this is a very cool feature for OjG.
- peterohler 6y agoAll good. You were polite and friendly or at least I read it that way.
- slingnow 6y agoIf you want something human readable why limit yourself to printing the raw JSON with different indentation rules? Just write a JSON "visualizer" that does something smart with the data. Your final example is just approaching a JSON -> YAML converter. If your complaint about your chosen human readable serialization format is that it isn't human readable enough, then switch to something more inherently human readable instead of writing tools to temporarily transform it.
- gpvos 6y agoI hadn't seen the SEN format before. I would like the keys unquoted as far as possible, but the commas kept in and otherwise also to keep it 100% Javascript-compatible and usable to cut and paste it into Javascript code.
- EdwardDiego 6y agoThe biggest advantage of the one line format is the ndjson/jsonl file where one line = one record.
- austincheney 6y agoI maintained a code beautification tool for about a decade. Here is what I learned from code beautification. 1. First notice that there is a world of difference between what users want and what they are willing to achieve. Know this more than anything else. People will ask for all kinds of shit, and.... A wish list is not a fully explored business requirement with known sub-tasks and test cases. A simple ask can become something worthy of a different independent project. 2. Too subjective. Everybody has subtle different personal preferences. In some cases the inability to support some edge case of some language will cause certain users to have an emotional episode. WTF. This is free software providing a convenience that you can easily live without. 3. A lot of work. You have to be very clear about what language, grammar, class of languages, or other various of characters you are willing to support. For example there is HTML then there are about billion trillion different HTML template schemes each with their own syntax and inside that syntax is a wildly different language than the surrounding HTML. 4. Carve out a measurable portion of your life. This is an investment of time you will never get back. Writing a code beautifier is far more work than it sounds. First, you need a parser. If one does not exist for the language you wish to support in the language or format of your tool you will need to write one. Be careful though, because that parser will have to support conventions that are unique to beautification and not necessarily useful elsewhere. In the case of the HTML example above you will need multiple different parsers that can achieve a nesting of parse trees or achieve harmony of a uniform parse tree beloved by all languages. This is achievable, as I have done it, but good luck. 5. Maintenance. There are always new edge cases, new languages, new grammars, new features and your users will want them all. Set hard boundaries. ------ With the amount of work required you will begin to ask yourself some basic life questions: Does this tool bring me more money or a better job? Does it bring me prestige AND satisfy a craving for attention? Does it improve my work, as in other real work outside your beautification tool? In my case, for a while, the tool did allow me access to better jobs with increased pay. It demonstrated I could do things many other developers could not and that I was willing to dedicate some absurd about of effort into something people actually used. But, that will only take your career so far after which you are just spinning your wheels and burning time. When I got further in my career I realized I wasn't beautifying my code ever. I had no need for the tool I was maintaining and despite continuous maintenance by me the tool started to decay, because the requirements had grown out of control and I was no longer an end user.
- _flux 6y agoAlso the revolution of two-letter command names :/. I mean, at least before one has proven a tool's ubiquitous use, use a longer name. jq just got lucky but I don't think it was because of its name ;).
- peterohler 6y agoOj is actually pretty well known as a Ruby JSON parser. The OjG project is in the same family.
- croes 6y agoWhat if the JSON has multiple nested objects?
- peterohler 6y agoWorks fine. Give it a try.
- olafure 6y agoFunny coincidence for cli tool name and the Icelandic meaning: https://en.wiktionary.org/wiki/oj#Interjection https://en.wiktionary.org/wiki/oj#Interjection
- peterohler 6y agoQuite a variety of meanings. Some pretty funny.
- specialist 6y agoNicely done. First I've seen "SEN". Treating the colons as white space, as you've done with the commas, will move you one step closer to The Correct Answer™.
- peterohler 6y agoI suppose that is possible to remove the colons but it is nice having the extra reminder that the left side of the colon is a key and the right a value. That could easily become lost if a new line is inserted after the key. SEN is new. After dealing with broken JSON due to commas missing or one at the end of an array and some of the team using Javascript this was a way of sucking in the broken JSON and fixing it.
- specialist 6y ago> After dealing with broken JSON... Postel tried to warn us. > ...nice having the extra reminder that the left side of the colon is a key and the right a value. Totally. IMHO: whitespace, formatting, delimiters are for humans. The parsers can do without. With some exceptions, like your examples of quoting strings to remove ambiguity.
- kccqzy 6y agoThe colons really aren't necessary in most cases. Clojure does away with the colons for instance. And I'm pretty sure GP is referring to some kind of Lisp.
- derefr 6y agoIMHO, this is what YAML is actually for. YAML is “a superset of JSON”, yes, but there are two separate meanings to that: • YAML has alternative syntactic sugar for expressing the same underlying JSON-equivalent semantics (sort of the same as Avro being canonically a binary compact expression of underlying JSON — in both cases, libraries for the codec expect JSON-encodable data structures as #encode input, and produce JSON-encodable data structures as #decode output) • YAML has its own semantics (like node type annotations, or references) that JSON doesn’t have, such that documents that use these are no longer transposable into JSON. I love bullet point #1. I hate bullet point #2. Personally, I wish there was a name for the reduced subset of YAML that is still a “syntactic superset of JSON”, but which has none of the extended semantics of bullet-point #2. Many systems that “consume YAML” already actually require their documents to be this “strictly-JSONifiable YAML”! Kubernetes, for example: it might seem to expose a YAML manifest API, but actually, internally, it does everything in JSON. All the resources in k8s etcd are stored in canonicalized JSON. The k8s controller just prettifies that JSON to YAML on its way out to you; and uglifies it back to JSON when you send it in. Which means that any YAML features that don’t survive that translation, can’t be used. IMHO, if YAML hadn’t been designed with any extended semantics, but instead had strictly targeted being a “sugared alternative encoding of JSON”, I think everyone would have switched to sending YAML in place of JSON a long time ago. Browsers would have likely added YAML parsing as well. But those added semantics are just so much extra work for everybody. Type annotations are source of so many vulnerabilities in programs that were unaware their input could “reach in and do things” through those types; and yet many YAML parser libs don’t have any flag to restrict them from decoding these type annotations (i.e. no way to “defuse the bomb.”) References change the entire way you have to write a YAML parser, disallowing some types of parsing grammar altogether, meaning you might no longer have access to the first-class parsing solution of your language runtime; meaning that for many runtimes, the YAML codec lib for that runtime is much slower — and memory-intensive! — than the JSON codec lib for the same runtime. Etc. Honestly, if we could all agree on a name for “strict, JSONifiable YAML”, and create libraries that only parse/validate/accept that subset of YAML while rejecting the higher-level semantics, those libs—and that interchange format—would be immediately more popular than YAML. The time for this to happen hasn’t passed! We still have a chance!
- 6y ago
- squaresmile 6y agoThe human style format reminds me of the default format of js-beautify [1]. We use it to get the "human-style" instead of the "One Line Per Node" for a project where we store json files in a git repo. That way the git diff is pretty easy to read and not bloated. Too bad, not many tools have the "human-style" option. [1] https://github.com/beautify-web/js-beautify https://github.com/beautify-web/js-beautify
- noxer 6y agoSlightly off topic but I sometimes use https://json.pizza https://json.pizza (a site I know from HN) to format JSON. It however does not have different ways to format just the standard indentation.
- Pxtl 6y agoI have trouble getting excited about tools to prettify JSON as long as the guy controlling the standard has a stubborn attitude about allowing comments or decent storage for long/multiline strings. At this point I honestly take XML over JSON where I have a choice because of CDATA and comments.
- ohitsdom 6y agoI was interested in this link just for its take on comments, bummed to see that skipped over.
- Garlef 6y ago> JSON can be made prettier by sorting the JSON object members by element keys. This seems to be a bad idea. The JSON language spec has ORDERED object members. But the order is arbitrary (precisely the one given in the JSON string) and does not have to be the lexicographic. Sorting the object members by default would introduce problems whenever the order matters to the consumer of the JSON.
- Garlef 6y agoI checked the spec again: It's unclear: At one point it says "An object is an unordered set of name/value pairs." while in the actual grammar it is ordered: object '{' ws '}' '{' members '}' members member member ',' members
- gmfawcett 6y agoI recommend reading the ECMA-404 spec instead. It's less ambiguous, and basically says that you can treat the pairs as ordered if you want, as the syntax itself doesn't imbue the order with any meaning: https://www.ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf https://www.ecma-international.org/wp-content/uploads/ECMA-4...
- dragonwriter 6y agoGrammar is inherently ordered, semantics is different from syntax.
- peterohler 6y agoI think you will find the JSON object members are not ordered while JSON array members are ordered. Since the JSON object members are not ordered, changing the order for display purposes does not change the data in any material way.
- Latty 6y agoThe spec[1] says: > An object is an unordered collection of zero or more name/value pairs, where a name is a string and a value is a string, number, boolean, null, object, or array. "whenever the order matters to the consumer of the JSON" should be never. More pragmatically, regardless of what the spec says, a ton of JSON tooling assumes the order doesn't matter and relying on it would be a big mistake. [1]: https://tools.ietf.org/html/rfc7159#section-1 https://tools.ietf.org/html/rfc7159#section-1
- dan-robertson 6y agoI tried using a json pretty printer in the lisp pp family of pretty printers (but it didn’t have miser mode.) Maybe we were just formatting things wrong and should have put brackets or breaking rules in different places, but changing that sort of code is hard and the results weren’t particularly great and people preferred the standard JSON.print(_,null,2) method. We switched to this and it was simpler and better. This format is also easier to process with something like grep or sed or awk or editor macros when needed.
- asaph 6y agoAn incremental formatting tweak is not a revolution.
- peterohler 6y agoThe title was meant to be fun. JSON format is a pretty light topic.
- breck 6y agoRelevant plug: if Pretty Notations interest you, then you should keep an eye on Tree Notation https://treenotation.org/ https://treenotation.org/.
- stevenpetryk 6y agoThis site uses only images to show the code but doesn't provide any text alternative for the image. Every image just has a `title` attribute of "Code you could hold in your hand"
- breck 6y agoLots of code examples here: https://jtree.treenotation.org/designer/ https://jtree.treenotation.org/designer/ And the source for that homepage is here: https://github.com/treenotation/treenotation.org https://github.com/treenotation/treenotation.org Always open to PR!
- adwn 6y agoa) That's not relevant, b) it's not nearly as new, interesting, or revolutionary as you think it is, and c) please stop spamming links to your website in every second thread here.
- breck 6y agohttps://giphy.com/gifs/smile-clap-laff-3oEjHI8WJv4x6UPDB6 https://giphy.com/gifs/smile-clap-laff-3oEjHI8WJv4x6UPDB6
- theamk 6y agoThe Tree Notation is like the opposite of pretty notation though, no? The whole idea of pretty notation is automatically inserting non-significant whitespace to make it look nice. Step 2, "one line per node", inserts spaces and newlines. Step 4, "human style" strategically removes some of those so the lines look nice -- the 2nd level dict has lots of content, so it was split across multiple lines... while the 3rd level dict has fewer data, so it all fits on one line. As opposed to this, Tree Notation is all about single canonical representation. So whitespace is significant, and you can never add or remove it to make output look nicer. You do whatever your schema tells you, and I hope you like many short lines.
- cratermoon 6y agoI recall seeing something that claimed all JSON is syntactically valid JavaScript. If that's correct, shouldn't it be possible to use JS code formatting engines to intelligently format JSON?
- sefrost 6y agoYes, Prettier can format JSON. https://prettier.io/docs/en https://prettier.io/docs/en
- binarymax 6y agoEasy node one-liner: JSON.stringify(JSON.parse(require('fs').readfileSync('myfile.json')),null,2);
- patdx 6y agoBasically yes, though `{` and `}` also start and end expression blocks in JavaScript. So the JS formatter needs to be aware that it is working with a JSON object and not a complete expression. Valid JSON: { "key1": "hello", "key2": "world" } You could "trick" a JS formatter to format it by wrapping with a fake function, etc. Some minimum valid JS: json({ key1: "hello", key2: "world", }); MongoDB has some JS libraries that use similar tricks to use JS parsers for their shell query format (which is similar to JSON). For example, around line 597: https://unpkg.com/browse/ejson-shell-parser@1.1.1/dist/ejson-shell-parser.cjs.dev.js https://unpkg.com/browse/ejson-shell-parser@1.1.1/dist/ejson...
- warmfuzzykitten 6y agoIt seems a mistake to format JSON as non-JSON text (SEN Format) in the name of "pretty". That will inevitably lead to copy/paste and monkey-see errors.
- theamk 6y agoI think that even you discard the last step ("sen") and stick to plain "human style with colors", this is already much prettier than many languages support. I like the idea that the incompatible format is off by default.
- soheilpro 6y agoAnother way to display JSON files in a more readable format is catj (https://github.com/soheilpro/catj https://github.com/soheilpro/catj)
- AtlasBarfed 6y agoI don't think YAML is perfect, but it is better than every one of these pretty formats. Pretty JSON is inevitably for either logging or config files, and YAML is better at both of those.
- jwfearn 6y ago`oj` looks like a useful tool. I wish it was Homebrew-installable.