[HN Gopher] Show HN: jql - Easier jq Alternative with a Lispy Sy...
       ___________________________________________________________________
        
       Show HN: jql - Easier jq Alternative with a Lispy Syntax Written in
       Go
        
       Author : cube2222
       Score  : 138 points
       Date   : 2020-01-07 16:00 UTC (7 hours ago)
        
 (HTM) web link (github.com)
 (TXT) w3m dump (github.com)
        
       | cookiecaper wrote:
       | To be frank the ascendance of jq has always seemed weird to me.
       | There are many alternatives that felt much more natural, as this
       | thread demonstrates.
       | 
       | JSONata remains pretty obscure but it's been my favorite JSON
       | query/mangling DSL for a while now: https://github.com/jsonata-
       | js/jsonata.
       | 
       | jmespath is also quite natural, but I think it was hurt by
       | reference implementation in Python, meaning it's quite slow.
       | http://jmespath.org/
        
         | rajangdavis wrote:
         | jq is simple to install, can handle large datasets (have used
         | it to parse GB's of JSON data), and works well with bash/shell.
        
       | pololee wrote:
       | I have used fx for a few days and enjoyed it.
       | https://github.com/antonmedv/fx
       | 
       | Highlights - interactive mode - Use full power of JavaScript.
       | 
       | $ curl ... | fx '.filter(x => x.startsWith("a"))'
       | 
       | - Access all lodash (or ramda, etc) methods by using .fxrc file.
       | 
       | $ curl ... | fx '_.groupBy("commit.committer.name")'
       | '_.mapValues(_.size)'
        
         | cube2222 wrote:
         | I've seen fx recently and it looks very interesting. jql is
         | obviously not as feature-rich, but you can achieve
         | interactivity with fzf!                 echo '' | fzf --print-
         | query --preview-window wrap --preview 'cat test.json | jql {q}'
        
       | xvilka wrote:
       | There is more universal alternative to jq that supports also
       | protobuf, msgpack, cbor, etc - rq[1].
       | 
       | [1] https://github.com/dflemstr/rq
        
         | cube2222 wrote:
         | Thanks, didn't know about it, looks interesting!
         | 
         | Though from the tutorial I don't really understand how I can
         | use it to transform data, other than converting between
         | formats.
        
           | dan-robertson wrote:
           | Looks like query support was dropped.
        
       | dan-robertson wrote:
       | I tend end to struggle with this sort of lispy syntax as I use a
       | regular shell (rather than one in emacs) which makes balancing
       | parens hard, and the syntax is otherwise verbose. If I'm using a
       | query tool like this it will be interactively writing a one-liner
       | so I want syntax that's easy to get right (balancing parens by
       | hand is hard), easy in the usual case (eg I think it should
       | accept some way to use symbols to name fields in a record because
       | typing and balancing double quotes all of the time is a waste of
       | time) and easy to append to (I will run a query, press up, move
       | to the end of the query, and modify. In this case I think moving
       | to the end involves counting parens).
       | 
       | Perhaps I would want some implicit pipe or threading at the top
       | level (which could be turned off with some option for scripts I
       | guess).
       | 
       | Another helpful feature could be adding the magic closing paren:
       | ] which will close all open parens to the top level. E.g. these
       | two lines are equivalent:                 (foo (bar (baz) whizz))
       | (foo (bar (baz) whizz]
       | 
       | A completely different (and less powerful) paradigm I like is one
       | of match-format where the pattern looks a lot like what it's
       | trying to match. In this case most functions and fancy
       | transformations are impossible. So you could write something like
       | this to extract the list of countries:                 j
       | '{countries: %%}'
       | 
       | Or to extract each country's name:                 j '{countries:
       | [ .*, { name: %% }, .*]}'
       | 
       | (Presumably you would have syntax for trying to match against
       | each array element). (I also would make commas optional but I'm
       | trying to make this syntax obvious).
       | 
       | Or to list the keys of the 0th element:                 j
       | '{countries: [ { %%: . }, .* ]}'
       | 
       | Or to get name and population:                 j '{countries: [
       | .*, { name: %name, population: %pop }]}'
       | 
       | The output might look like:                 { "name": "Poland",
       | "population": "38000000" }
       | 
       | ----------------
       | 
       | An alternative query tool which I haven't really seen before
       | would be one which is interactive rather than repeatedly run: you
       | pipe your json (or whatever) into it and it spins up some kind of
       | (curses) gui shoeing you your data with a nice interface to
       | interactively write your query and see its results. It could
       | finish by outputting a command line to run the query non-
       | interactively.
       | 
       | The closest thing to this I know of is a small programming by
       | example demo that came out of Microsoft research. The goal was to
       | figure out how to extract some desired data from some big blob of
       | json (or maybe html, I don't remember). The user would see the
       | json in some web gui and could click on the data they wanted and
       | the app would try to figure out the "most obvious" query to get
       | it. If they clicked on the country name, I think it would
       | probably suggest a query for all the countries' names (but maybe
       | that would be the second option). I think it would also try to be
       | clever about grouping, so if the user picked name and population
       | it would hopefully figure out that they should come in pairs
       | rather than being two independent lists that might not be the
       | same length.
        
         | cube2222 wrote:
         | Thanks so much for so much feedback!
         | 
         | There's already an issue to automatically add trailing parens
         | and I definitely think it's a good idea, as balancing parens
         | really is annoying in-shell. I didn't think of it before.
         | 
         | I decided that adding interactivity would be too much work, but
         | you could definitely put together a oneliner (or alias) which
         | achieves that using fzf: https://paweldu.dev/posts/fzf-live-
         | repl/
         | 
         | Overall I encourage you to try write a tool that has the syntax
         | you described, as it sounds plausible! It kinda looks a little
         | bit like graphql, so maybe that would fit?
        
           | dan-robertson wrote:
           | I wonder if it's worth writing some read line macros (and
           | instructions for setting them up) for eg inserting ( ) <move-
           | cursor-backwards>. (Bound to M-( in emacs) I think they help
           | for simple sexp editing. I also wish I had something like
           | "run this command but when it's done show me a prompt with
           | the same command and my cursor in the same place" but I have
           | no idea if it's easy
        
             | cube2222 wrote:
             | I didn't know about readline macros, thanks for mentioning
             | them.
             | 
             | I'll think about it.
        
         | cube2222 wrote:
         | I've written the interactivity oneliner:                 echo
         | '' | fzf --print-query --preview-window wrap --preview 'cat
         | test.json | jql {q}'
        
       | mharju wrote:
       | Looks cool!
       | 
       | I'm using https://github.com/borkdude/babashka with with
       | https://github.com/borkdude/jet and a few aliases along with curl
       | for the same effect.
       | 
       | The syntax of jq is so hard to remember a familiar lisp always
       | beats it :)
        
         | cube2222 wrote:
         | Nice, this looks great, and I love Clojure too, so I'll make
         | sure to check it out!
        
       | lcfcjs2 wrote:
       | Syntax is too confusing, I'll stick with jq thanks
        
       | onionisafruit wrote:
       | Nice project. I'm a heavy jq user and still find myself regularly
       | tinkering around with queries on https://jqplay.org. It would be
       | nice if there were a similar page for jql, even if it is just a
       | local server.
        
         | cube2222 wrote:
         | I was thinking of an interactive terminal app, but that would
         | be much more work.
         | 
         | Though you can surely put a oneliner together which achieves
         | the same effect using fzf, as per:
         | https://paweldu.dev/posts/fzf-live-repl/
        
           | preek wrote:
           | Here's an interactive implementation (in Emacs):
           | https://github.com/200ok-ch/counsel-jq
        
         | cube2222 wrote:
         | Done:                 echo '' | fzf --print-query --preview-
         | window wrap --preview 'cat test.json | jql {q}'
        
       | moondev wrote:
       | Personally I like https://github.com/mikefarah/yq
       | 
       | It also converts between yaml and json with ease for writing and
       | reading
        
       | spikepuppet wrote:
       | I'm super excited to sink my teeth into this. I usually have to
       | use jq in intense bursts and no matter what i end up back in the
       | manual because it just get's so damn confusing.
       | 
       | From a quick look on the repo, this seems so much simpler.
       | Thanks!!!
        
         | cube2222 wrote:
         | I'm happy that you're so enthusiastic! Make sure to let me know
         | what you think about it after having tried it out!
        
       | preek wrote:
       | If you're into jq, here's an interface so you can have live
       | feedback (in Emacs): https://github.com/200ok-ch/counsel-jq
        
         | cube2222 wrote:
         | You can probably also use an analogue to:                 echo
         | '' | fzf --print-query --preview-window wrap --preview 'cat
         | test.json | jql {q}'
         | 
         | Something close to:                 echo '' | fzf --print-query
         | --preview-window wrap --preview 'cat test.json | jq {q}'
        
       | michaelmrose wrote:
       | If this was lispy then (elem "countries" (elem 0)
       | 
       | Would be an error.
       | 
       | Expression evaluation ought to be inside out.
        
         | cube2222 wrote:
         | Why?
         | 
         | It is, it's just that everything returns a function! The
         | topmost function will in the end be called with the json as an
         | argument.
         | 
         | Check out these parts of the README:
         | https://github.com/cube2222/jql#attention
         | https://github.com/cube2222/jql#type-cheatsheet
         | 
         | (elem 0) returns a function which takes a JSON array as input
         | and returns its first element.
        
           | michaelmrose wrote:
           | Can you find many lisps or even any lisps that evaluate like
           | that? A familiar syntax that actually works different from
           | any lisp in existence may be more hindrance than help.
        
       | murat124 wrote:
       | echo '' | fzf --print-query --preview-window wrap --preview 'cat
       | test.json | jql {q}'
       | 
       | Trivial note. echo instead of echo '' or echo "".
       | $> [[ "$(echo)" == "$(echo '')" ]] && echo $? || echo $?       $>
       | 0
        
       | fiatjaf wrote:
       | Shameless plug to my list of interesting `jq` stuff:
       | https://github.com/fiatjaf/awesome-jq
       | 
       | It even features a complete rewrite of the actual jq in Go.
        
       | EdSchouten wrote:
       | So I guess we all know https://jsonnet.org, right? It's
       | advertised as a data templating language. Interestingly enough,
       | you can also use it as a querying tool. The first four examples
       | on the jql page can be translated to Jsonnet as follows:
       | $ jsonnet -e "(import 'test.json').countries[0]"         $
       | jsonnet -e "(import 'test.json').countries[0:2]"         $
       | jsonnet -e "[x.name for x in (import 'test.json').countries]"
       | $ jsonnet -e "std.objectFields((import
       | 'test.json').countries[0])"
       | 
       | With that in mind, I never bothered to learn how to use a tool
       | like jq, rq, jql, etc.
        
         | ljm wrote:
         | My favourite is yq, because there are two competing and utterly
         | incompatible implementations of it. Just to add onto the
         | shitshow that YAML can be.
        
         | FragenAntworten wrote:
         | Another option which looks interesting:
         | https://github.com/jmespath/jp
         | 
         | I haven't used it yet, but the syntax seems simpler than some.
        
           | edaemon wrote:
           | Thanks for the link, this straightforward syntax is what I've
           | been after for dealing with JSON on the command line.
        
         | hnlmorg wrote:
         | Shameless plug but I've also written a language (murex) for
         | shell scripting which can be used for this too:
         | open test.json -> [ countries ]         open test.json -> [[
         | /countries/0 ]]         open test.json -> [ countries ] -> [ 0
         | 2 ]
         | 
         | Single brackets only return the next level deep but can
         | retrieve multiple items (and negative integers to count from
         | the end of an array). Double brackets are to specify a path. So
         | [[/foo/bar]] is literally the same as [foo]->[bar]
         | 
         | Lastly the 4th example:                   open test.json -> [
         | countries ] -> foreach c { echo $c[name] }
         | 
         | Unfortunately this one doesn't output it in JSON. If you needed
         | that you could chain another command to reformat it:
         | open test.json -> [ countries ] -> foreach c { echo $c[name] }
         | -> cast str -> format json
         | 
         | This works because murex can auto-convert between lists, JSON,
         | YAML, CSV and a few other structured formats. However it's fair
         | to say it is a lot more verbose than jsonnet and jql in that
         | last example.
         | 
         | Github repo: https://github.com/lmorg/murex
         | 
         | I've been using this as my primary shell for a few years now
         | and, like yourself, I've never bothered to learn jq because of
         | that.
         | 
         | NB all of the above example are running from inside the murex
         | interactive command line (like bash). If you wanted to run it
         | like jq/jql then you'd need to do the same sort of thing as
         | bash:                   murex -c 'open test.json -> [ countries
         | ]'
        
         | cube2222 wrote:
         | This is actually the best alternative to jq for me I've seen so
         | far! (other than jql obviously ;) ) Thanks for posting!
        
         | jaib8 wrote:
         | I haven't used jsonnet before but is it possible to read via
         | stdin? for eg to parse a json response from a rest endpoint in
         | jq I would do something like: $ curl "..." | jq .abc
        
         | theamk wrote:
         | > Language Reference
         | 
         | > TODO: This page still needs to be written.
         | 
         | Looks like it is not ready for prime time yet... (Not to
         | mention that it seems to have no support for stdin input,
         | multiple input files, and json lines format - which are my main
         | uses cases for jq)
        
       | cube2222 wrote:
       | Hey, author here. Christmas Eve, 22 pm, after having eaten with
       | my family, I was browsing HN (because what else could you be
       | doing then), and stumbled upon this comment:
       | https://news.ycombinator.com/item?id=21860107
       | 
       | It inspired me to create an alternative to jq with more of a
       | Lispy syntax, as I think the original is awesome but also fairly
       | cryptic for anything more advanced than single field selection.
       | 
       | Overall it was a fun few-day project, and was also very
       | educational in terms of writing a parser in Go. (I used goyacc
       | before in the sql parser of OctoSQL[1], however, that one is
       | copied from vitess, so I've never built one from scratch, only
       | customised an existing one. It's really pleasant overall and the
       | code is very simple, so I encourage you to take a look[2].
       | 
       | I'd love to hear any feedback, comments or potential improvements
       | you can think of.
       | 
       | [1]:https://github.com/cube2222/octosql
       | 
       | [2]:https://github.com/cube2222/jql/tree/master/jql/parser
        
         | jsd1982 wrote:
         | Looks good but there seems to be a missing explanation gap
         | between switching from `(elem "countries")` and dropping the
         | `elem`s to just `("countries")`. Why is `elem` able to be
         | dropped? Is it the default function or something? I didn't
         | follow that big jump in the readme.
        
           | cube2222 wrote:
           | It's explained right below, I think I messed up the order
           | there.
           | 
           | Is this understandable? "You can see that elem is the most
           | used function, and in fact it's what you'll usually be using
           | when munging data, so there's a shortcut. If you put a value
           | in function name position, it implicitly converts it to an
           | elem."
           | 
           | EDIT: Fixed the order.
        
         | daxelrod wrote:
         | Nicely done!
         | 
         | I scratched a similar itch by creating Jowl[1], which uses
         | JavaScript one-liners with the Lodash library to transform
         | JSON.
         | 
         | Like you, I wanted syntax that was more familiar to me than
         | jq's, and like you, I wanted to use a language that was built
         | for data structure transformation.
         | 
         | However, I offloaded all of the actual hard work to an existing
         | JavaScript runtime, and to an existing library for data
         | structure transformation, meaning I could punt on the parser
         | and language design; the hard parts that you've taken on. I
         | have this bookmarked to read through in more detail later this
         | week.
         | 
         | [1]: https://github.com/daxelrod/jowl
        
           | cube2222 wrote:
           | Sounds great! I'll make sure to check out out!
           | 
           | My approach was caused by the fact that I wanted to have a
           | minimal language which only contains what really is necessary
           | and is as regular as possible, to be very simple as a result.
        
         | downerending wrote:
         | Looks great!
         | 
         | As an old Lisper, I did get a wry chuckle out of the fact that
         | you managed to avoid Lisp for a tool that uses Lispy syntax.
         | :-)
        
           | cube2222 wrote:
           | Thanks!
           | 
           | Yup, I felt the irony myself creating it, as I actually love
           | clojure!
           | 
           | But I'm most comfortable with Go, and it results with a
           | single binary for any of the major platforms with very quick
           | startup time (as illustrated by the small benchmark), so
           | those were huge advantages.
        
             | useragent86 wrote:
             | I love the idea of a Lispy version of jq, but as a Lisper,
             | the examples feel bewildering to me. The fact that (elem)
             | takes an optional second argument, whose default value is
             | 'identity, seems familiar, but then I try to understand
             | this example, and it just doesn't make sense to me:
             | (elem "countries" (elem (keys) (elem "name")))
             | 
             | I think part of the problem is, IIUC, this is constructing
             | a pipeline, like (->) in Clojure, but is using function
             | call syntax rather than pipeline syntax, and also mixing in
             | a function call with (keys). I feel like it's a very non-
             | Lispy language masquerading as Lisp by wearing parens.
             | 
             | Instead, if you wrote it as real Lisp function calls, and
             | provided a real pipeline macro, it would be much more Lispy
             | and, I think, much easier to understand. For example,
             | instead of:                   (elem "countries" (elem
             | (keys) (elem "name")))
             | 
             | Either of these alternative syntaxes:                   (->
             | "countries" (key "name"))         (key "name"
             | ("countries"))
             | 
             | Would return the same result:                   ["Poland",
             | "United States",          "Germany"]
             | 
             | While being more concise and more Lispy.
             | 
             | I also wouldn't mind using an anaphoric 'mapcar, something
             | like:                   (-> "countries" (map-> (key
             | "name"))
             | 
             | Otherwise it may be unclear which functions implicitly map
             | across entries and which don't.
             | 
             | My two cents. Thanks for sharing your work.
        
               | cube2222 wrote:
               | Thanks for the feedback!
               | 
               | Have you checked out the type cheatsheet? (keys) is a
               | function call!
               | 
               | The function you pass as last to elem is kind of a
               | continuation. It's a function which transforms the output
               | before returning.
               | 
               | All you're doing writing a jql query is composing one big
               | function.
               | 
               | With values in function call position, it's just that
               | (keys) gets evaluated to a value (the keys of the map)
               | and that is used to index the json. That's why keys
               | wouldn't work, you need the parentheses to make it a
               | function call.
               | 
               | This may sound complex at first but I find it gets
               | intuitive quickly.
               | 
               | EDIT: Check out this comment thread on reddit as I think
               | the commenter may have had similar problems: https://www.
               | reddit.com/r/golang/comments/ehnsz5/jql_json_que...
        
               | useragent86 wrote:
               | > Have you checked out the type cheatsheet?
               | 
               | I'm afraid that that does not help me understand how
               | (elem) works at all. From my perspective, I don't need a
               | "type cheatsheet," I need elem's docstring that explains
               | what arguments it expects and what it returns. I'm not
               | even thinking about value types yet, and I don't know
               | what grammar that cheatsheet is written in, nor why I
               | would need to read one to be able to use what seems like
               | the most basic function jql has, the elem function.
               | 
               | To write clear, useful documentation, you need to put
               | yourself in the shoes of a user who has never seen your
               | project before, who has not walked the miles you have to
               | arrive at the solutions you have, and take them quickly
               | to the same destination.
               | 
               | The readme, in general, is another issue. It's...not
               | ideal.
               | 
               | The first part tries to sound cute with stuff like, "Hey
               | there!" and, "remember? That's explicitly not why we're
               | here. But it aids understanding of the more complex
               | examples, so stay with me just a little bit longer!".
               | 
               | But I'm not a little kid who's bored in math class, so
               | writing like that turns me off quickly. Having to read
               | through long prose like, "Ok, let's check it out now, but
               | first things first, you have to install it:" instead of
               | simply a heading titled "Install:" feels like I'm wasting
               | my time, and I quickly skip ahead or move on to the next
               | HN article.
               | 
               | What I'm really looking for is a short intro and a table
               | of contents with sections like: Intro, Examples,
               | Tutorial, Reference, FAQ.
               | 
               | You might think of it like this: the project is your
               | baby, but just like in real life, no one thinks your
               | baby's as cute as you do, and, no, we don't want to see
               | the baby pictures. ;)
        
               | cube2222 wrote:
               | I actually don't think like that. I even have another
               | fairly successful open source project whose readme is
               | probably more to your liking, and is nothing like this
               | one: https://github.com/cube2222/octosql
               | 
               | Everybody has different taste. I like README's like that
               | as they add an entertainment value. Overall I've got two
               | kinds of feedback about the README: 1. I really like the
               | project and really liked the readme, great fun to read!
               | 2. The readme was very confusing on top of the already
               | confusing query language.
               | 
               | And based on that I'm planning to keep it unchanged for
               | now. I added the type cheatsheet to make case 2 at least
               | a little bit better.
               | 
               | As for the quick examples, you can just scroll through
               | the code blocks (as each one contains input with its
               | corresponding output).
        
               | useragent86 wrote:
               | Since you're discarding feedback kind #2, and all of the
               | potential users it represents, in favor of entertainment
               | value, I'd say that you _do_ think like that. ;)
               | Comedians write lots of jokes, but when they stand up and
               | tell them in front of an audience, they find out which
               | ones work and which ones don 't. How they respond to that
               | feedback determines their success. Hey, it's your
               | project.
        
               | cube2222 wrote:
               | I'm definitely not discarding it! I'm open to writing
               | additional clarifying paragraphs. (that's why I wrote the
               | type cheat sheet). I'm also not discarding feedback kind
               | #1 :)
               | 
               | I'm thinking of adding another one which shows how
               | standard map/filter notation maps to jql queries.
               | 
               | Anyways, thanks for the feedback again, I do appreciate
               | it!
        
               | useragent86 wrote:
               | Regarding CPS: I think that expecting users to write
               | queries in CPS is going to be very limiting. It's missing
               | a big opportunity to use simple funcall and/or pipeline
               | syntax, with which many more users are familiar. Even
               | among Lispers, CPS isn't that popular. But Lisp, in
               | general, could make jq-like syntax much more regular and
               | usable.
        
               | cube2222 wrote:
               | I find it gets intuitive without being familiar with CPS
               | (I wasn't before).
               | 
               | In practice, thanks to this, the resulting queries end up
               | matching the input JSON structure very well.
        
               | mst wrote:
               | I was seeing it as roughly:                 (elem x f)
               | 
               | is sugar for                 (filter-on f (elem x))
               | 
               | or so? (filter-on imaginary thing but hopefully obvious
               | enough as pseudocode)
        
               | cube2222 wrote:
               | Almost! It's more like (map f (elem x JSON)), where map
               | gets changed to apply, if (elem x JSON) results in a
               | single value (non-collection).
        
         | yamafaktory wrote:
         | Hi, awesome work! Love the idea of using S-expression for the
         | query syntax! Funny fact: we share the same project name - even
         | though the core language and the query syntax are different -
         | see https://github.com/yamafaktory/jql.
        
           | cube2222 wrote:
           | Hey, looks really nice! I see we had similar goals of
           | simplicity in mind, though with very different approaches.
           | 
           | Sorry for the name duplication! I thought I'm sharing the
           | name only with the jira API jql.
           | 
           | EDIT: I'd love to see some benchmarks. I expect your jql to
           | greatly outperform mine and jq, considering it's written in
           | Rust!
        
             | yamafaktory wrote:
             | No worries regarding the naming, it's what makes open-
             | source friendly! You can see some benches here e.g.
             | https://travis-ci.org/yamafaktory/jql/jobs/618374730#fold-
             | st... but those actually more like regression tests that
             | are triggered for pull requests to compare them against
             | master (and I should use Github actions probably here now
             | instead of Travis). Thanks for your reply!
        
       | markandrewj wrote:
       | This is fairly short talk, but it might interesting to some
       | people.
       | 
       | https://youtu.be/PS_9pyIASvQ [Serious Programming with jq?! A
       | practical and Purely Functional Programming Language!]
        
         | cube2222 wrote:
         | Thanks for posting! I'll make sure to watch it!
        
           | markandrewj wrote:
           | No problem. I hope it is helpful and interesting for you.
        
       | ldd wrote:
       | only tangentially related, I made a VS Code extension to help out
       | with jq development[0].
       | 
       | Also, jq supports modules. which is awesome!
       | 
       | [0]https://marketplace.visualstudio.com/items?itemName=ldd-
       | vs-c...
        
       ___________________________________________________________________
       (page generated 2020-01-07 23:00 UTC)