alec
alec

Reputation: 395

what's the simplest way to calculate the sum of values at the end of this jq command?

I see that jq can calculate addition as simply as jq 'map(.duration) | add' but I've got a more complex command and I can't figure out how to perform this add at the end of it.

I'm starting with data like this:

{
  "object": "list",
  "data": [
    {
      "id": "in_1HW85aFGUwFHXzvl8wJbW7V7",
      "object": "invoice",
      "account_country": "US",
      "customer_name": "clientOne",
      "date": 1601244686,
      "livemode": true,
      "metadata": {},
      "paid": true,
      "status": "paid",
      "total": 49500
    },
    {
      "id": "in_1HJlIZFGUwFHXzvlWqhegRkf",
      "object": "invoice",
      "account_country": "US",
      "customer_name": "clientTwo",
      "date": 1598297143,
      "livemode": true,
      "metadata": {},
      "paid": true,
      "status": "paid",
      "total": 51000
    },
    {
      "id": "in_1HJkg5FGUwFHXzvlYp2uC63C",
      "object": "invoice",
      "account_country": "US",
      "customer_name": "clientThree",
      "date": 1598294757,
      "livemode": true,
      "metadata": {},
      "paid": true,
      "status": "paid",
      "total": 57000
    },
    {
      "id": "in_1H8B0pFGUwFHXzvlU6nrOm6I",
      "object": "invoice",
      "account_country": "US",
      "customer_name": "clientThree",
      "date": 1595536051,
      "livemode": true,
      "metadata": {},
      "paid": true,
      "status": "paid",
      "total": 20000
    }
  ],
  "has_more": true,
  "url": "/v1/invoices"
}

and my jq command looks like:

cat example-data.json | 
  jq -C '[.data[] 
          | {invoice_id: .id, client: .customer_name, date: .date | strftime("%Y-%m-%d"), amount: .total, status: .status}
          | .amount = "$" + (.amount/100|tostring)]
         | sort_by(.date)'

which nicely gives me output like:

[
  {
    "invoice_id": "in_1H8B0pFGUwFHXzvlU6nrOm6I",
    "client": "clientThree",
    "date": "2020-07-23",
    "amount": "$200",
    "status": "paid"
  },
  {
    "invoice_id": "in_1HJlIZFGUwFHXzvlWqhegRkf",
    "client": "clientTwo",
    "date": "2020-08-24",
    "amount": "$510",
    "status": "paid"
  },
  {
    "invoice_id": "in_1HJkg5FGUwFHXzvlYp2uC63C",
    "client": "clientThree",
    "date": "2020-08-24",
    "amount": "$570",
    "status": "paid"
  },
  {
    "invoice_id": "in_1HW85aFGUwFHXzvl8wJbW7V7",
    "client": "clientOne",
    "date": "2020-09-27",
    "amount": "$495",
    "status": "paid"
  }
]

and I want to add a sum/total at the end of that, something like Total: $1775, so that the entire output would look like this:

[
  {
    "invoice_id": "in_1H8B0pFGUwFHXzvlU6nrOm6I",
    "client": "clientThree",
    "date": "2020-07-23",
    "amount": "$200",
    "status": "paid"
  },
  {
    "invoice_id": "in_1HJlIZFGUwFHXzvlWqhegRkf",
    "client": "clientTwo",
    "date": "2020-08-24",
    "amount": "$510",
    "status": "paid"
  },
  {
    "invoice_id": "in_1HJkg5FGUwFHXzvlYp2uC63C",
    "client": "clientThree",
    "date": "2020-08-24",
    "amount": "$570",
    "status": "paid"
  },
  {
    "invoice_id": "in_1HW85aFGUwFHXzvl8wJbW7V7",
    "client": "clientOne",
    "date": "2020-09-27",
    "amount": "$495",
    "status": "paid"
  }
]
Total: $1775

Is there a neat/tidy way to enhance this jq command to achieve this?
Or even, since I'm invoking this in a shell script, a dirty/ugly way with bash?

Upvotes: 1

Views: 544

Answers (2)

Charles Duffy
Charles Duffy

Reputation: 295679

If any of your output is going to be raw, you need to pass -r; it'll just be ignored for data items that aren't strings.

Anyhow -- if you write (expr1, expr2), then your input will be passed through both expressions. Thus:

jq -Cr '
  ([.data[]
    | {invoice_id: .id,
       client: .customer_name,
       date: .date | strftime("%Y-%m-%d"),
       amount: .total,
       status: .status}
    | .amount = "$" + (.amount/100|tostring)
   ] | sort_by(.date)),
  "Total: $\([.data[] | .total] | add | . / 100)"
'

Upvotes: 5

peak
peak

Reputation: 116900

In case you decide after all to emit valid JSON, here is a modular answer to the question that makes it easy to formulate alternative approaches, and which postpones the conversion of .amount to dollars for efficiency:

def todollar:
  "$" + tostring;
  
def json:
  [.data[]
   | {invoice_id: .id,
      client: .customer_name,
      date: .date | strftime("%Y-%m-%d"),
      amount: (.total/100),
      status: .status} ]
  | sort_by(.date) ;

json
| map_values(.amount |= todollar),
  "Total: " + (map(.amount) | add | todollar)

As noted elsewhere, you will probably want to use the -r command-line option.

Upvotes: 2

Related Questions