fx - jq alternative for handling JSON from the command line


    jq is the most popular command line JSON utility written in C and has its own syntax for working with JSON.


    However, you do not need to process JSON on the command line very often, and when the need arises, you have to suffer from an unfamiliar programming language.


    So the idea came up to write fx with a simple and clear syntax that you will never forget. And what programming language do everyone know? That's right - JavaScript.


    fx takes JavaScript code containing an anonymous function as an argument and calls it, substituting the JSON obtained from stdin as an argument. That the function returns will be output to stdout. All. No more complicated and obscure designs.


    Compare two solutions to the same problem on jq and fx:


    jq '[.order[] as $x | .data[$x]]'

    fx 'input => input.order.map(x => obj.data[x])'

    A little more verbose? Yes, it's just plain javascript.


    If fx does not pass arguments at all, then JSON will be formatted and output:


    $ echo '{"key":"value"}' | fx
    {
        "key": "value"
    }

    If the code does not contain param =>, then the transferred code will be automatically converted to an anonymous function and thiswill contain the passed JSON object:


    $ echo '{"foo": [{"bar": "value"}]}' | fx 'this.foo[0].bar'
    "value"

    fx it is possible to pass several arguments / anonymous functions, they will be applied alternately to JSON:


    $ echo '{"foo": [{"bar": "value"}]}' | fx 'x => x.foo' 'this[0]' 'this.bar'
    "value"

    If the code contains a keyword yield, the created anonymous function will contain a “deployed” generator (example from generator-expression ):


    $ cat data.json | fx '\
    for (let user of this) \ 
      if (user.login.startsWith("a")) \
        yield user'

    $ echo '["a", "b"]' | fx 'yield* this'
    [
        "a",
        "b"
    ]

    This allows you to describe very simple selections in complex queries.


    By the way, modifying JSON with fx is also very simple:


    $ echo '{"count": 0}' | fx '{...this, count: 1}'
    {
        "count": 1
    }

    You can also use any npm package if you install it globally:


    $ npm install -g lodash
    $ cat package.json | fx 'require("lodash").keys(this.dependencies)'

    For some, it’s important that jq is just one binary (~ 2mb). So fx also has separate binaries .


    They weigh a little more (~ 30mb), but if this is not critical for you and nodejs is worth it, then you can install fx using npm:


    npm install -g fx

    And what about performance? Let's measure with hyperfine :


    $ curl 'https://api.github.com/repos/stedolan/jq/commits' > data.json
    $ hyperfine --warmup 3 "jq ..." "fx ..."
    Benchmark #1: cat data.json | jq '[.[] | {message: .commit.message, name: .commit.committer.name}]'
      Time (mean ± σ):      10.7 ms ±   0.9 ms    [User: 8.7 ms, System: 3.6 ms]
      Range (min … max):     9.0 ms …  14.9 ms
    Benchmark #2: cat data.json | fx 'this.map(({commit}) => ({message: commit.message, name: commit.committer.name}))'
      Time (mean ± σ):     159.6 ms ±   4.4 ms    [User: 127.4 ms, System: 28.4 ms]
      Range (min … max):   153.0 ms … 170.0 ms
    

    An order of magnitude less. And the thing is that fx uses evalto run the code from the arguments (and in general the whole code fx <70 lines of code). If processing speed is important to you, do not use fx. In all other cases, fx is an excellent choice.



    I hope this utility is useful to someone. Thanks for attention.


    Also popular now: