Reputation: 55
There is an input file which contains several objects connected to one specific day. (In this case it's 4th of October). Each Object however, represents another time of that day, with different VALUES. "ID" and day are always the same in the file:
INPUT.json:
[
{
"DATE": "04.10.2017 10:20",
"ID":"x",
"VALUE_ONE": 20,
"VALUE_TWO": 3
},
{
"DATE": "04.10.2017 12:50",
"ID":"x",
"VALUE_ONE": 40,
"VALUE_TWO": 5
},
{
"DATE": "04.10.2017 14:20",
"ID":"x",
"VALUE_ONE": 10,
"VALUE_TWO": 2
}
]
What I want to do now is calculate the day's total values and also do some math. (in this case either add or multiply by 3).
The results shall be added to the 'consolidated.json' file, which should contain one object for each day. To ensure that, this process will be repeated daily.
I have commented the calculation steps behind '//' :
CONSOLIDATED.json:
[
{
"DATE": "02.10.2017",
"VALUE_ONE_TODAY": 40,
"VALUE_ONE_TOTAL": 800,
"VALUE_THREE_TODAY": 5,
"VALUE_THREE_TOTAL": 110
},
{
"DATE": "03.10.2017",
"VALUE_ONE_TODAY": 90,
"VALUE_ONE_TOTAL": 890, // =800+90
"VALUE_THREE_TODAY": 8,
"VALUE_THREE_TOTAL": 134 // = 110 + 3*8
},
{ //this object is new!
"DATE": "04.10.2017",
"VALUE_ONE_TODAY": 70,
"VALUE_ONE_TOTAL": 960, // =890+70
"VALUE_THREE_TODAY": 10,
"VALUE_THREE_TOTAL": 164 // =134 +3*10
}
]
I know I can get the totals of INPUT.json with
[.[]|.VALUE_ONE]|add
and
[.[]|.VALUE_TWO*3]|add
But I am not sure how to get this into the desired structure and use last day's totals. Thanks!
Upvotes: 0
Views: 1276
Reputation: 116919
Here is a straightforward solution, which for simplicity of exposition makes the following (easy-to-work-around) assumptions:
def aggregate:
{DATE: (.[0]|.DATE|split(" ")[0]),
VALUE_ONE_TODAY: (map(.VALUE_ONE) | add),
VALUE_TWO_TODAY: (map(.VALUE_TWO) | add)}
;
.[-1] as $previous
| ($i | aggregate
| {DATE,
VALUE_ONE_TODAY,
VALUE_ONE_TOTAL : ($previous.VALUE_ONE_TOTAL + .VALUE_ONE_TODAY),
VALUE_THREE_TODAY : ($previous.VALUE_TWO_TOTAL + .VALUE_TWO_TODAY) }
| .VALUE_THREE_TOTAL = ($previous.VALUE_THREE_TOTAL + 3 * .VALUE_THREE_TODAY) ) as $today
| . + [$today]
jq -f consolidate.jq --argfile i input.json consolidated.json
If you are sure it is safe to do so, you could tack on: | sponge consolidated.json
The added entry is as requested, namely:
{
"DATE": "04.10.2017",
"VALUE_ONE_TODAY": 70,
"VALUE_ONE_TOTAL": 960,
"VALUE_THREE_TODAY": 10,
"VALUE_THREE_TOTAL": 164
}
Upvotes: 0
Reputation: 14705
Here is a solution:
#!/bin/bash
jq -M --argfile i input.json '
def VALUE_keys: keys[] | select(startswith("VALUE"));
def TODAY_keys: keys[] | select(endswith("TODAY"));
def TODAY_($k): "\($k)_TODAY";
def TOTAL_($k): "\($k)_TOTAL";
def new_totals:
$i
| (map(VALUE_keys)|unique) as $attrs
| reduce (.[]|.DATE |= .[:10]) as $d ({}
; reduce $attrs[] as $a (.
; [$d.DATE, $a] as $p | setpath($p; getpath($p)+$d[$a])
)
)
| keys[] as $date
| [$date, .[$date]] ;
def aggregate($k; $total):
.[TODAY_($k)] = $total
| if $k == "VALUE_THREE"
then .[TOTAL_($k)] += $total * 3
else .[TOTAL_($k)] += $total
end ;
def next_day:
foreach new_totals as [$date, $totals] (
max_by(.DATE)
; if .DATE == $date then empty else . end
| .DATE = $date
| .[TODAY_keys] = 0
| reduce ($totals|keys[]) as $k (.; aggregate($k; $totals[$k]))
)
;
. + [next_day]
' consolidated.json | sponge consolidated.json
new_totals
computes the input.json
sums returning the date and an array of values. From the sample data it produces:
[
"04.10.2017",
{
"VALUE_ONE": 70,
"VALUE_TWO": 10
}
]
next_day
applies these values to the latest entry of consolidated.json
returning a value for the next day. E.g.
{
"DATE": "04.10.2017",
"VALUE_ONE_TODAY": 70,
"VALUE_ONE_TOTAL": 960,
"VALUE_THREE_TODAY": 0,
"VALUE_THREE_TOTAL": 134,
"VALUE_TWO_TODAY": 10,
"VALUE_TWO_TOTAL": 10
}
Note that the example output is inconsistent with the sample input.json
so
the values differ.
The script adds this to the input and uses sponge
to update consolidated.json
with the new data. Note that the line
| if .DATE == $date then empty else . end
prevents it from adding the data for a new day if that day is already present. This is unnecessary if other safeguards are in place.
Note also that next_day
uses foreach
to handle the situation where input.json
contains data for more then one day. For example if the first element of input.json
were
{
"DATE": "03.12.2017 10:20",
"ID":"x",
"VALUE_ONE": 20,
"VALUE_TWO": 3
}
next_day
would generate two entries:
{
"DATE": "03.12.2017",
"VALUE_ONE_TODAY": 20,
"VALUE_ONE_TOTAL": 910,
"VALUE_THREE_TODAY": 0,
"VALUE_THREE_TOTAL": 134,
"VALUE_TWO_TODAY": 3,
"VALUE_TWO_TOTAL": 3
}
{
"DATE": "04.10.2017",
"VALUE_ONE_TODAY": 50,
"VALUE_ONE_TOTAL": 960,
"VALUE_THREE_TODAY": 0,
"VALUE_THREE_TOTAL": 134,
"VALUE_TWO_TODAY": 7,
"VALUE_TWO_TOTAL": 10
}
Upvotes: 1