Array push with autovivification in jq
I wanted to make a note to self about this. I'm using Advent of Code for an opportunity to practise and learn more about jq
, and in Day 7: No Space Left On Device I think I need a way of appending values to arrays, which are themselves values of properties that I create on the fly. This may not turn out to be useful in the end, but I wanted to explore it (I was thinking I could store the list of files in a given directory like this).
See the update at the end of this post for a much neater approach.
The structure I had in mind is this (in pseudo-JSON):
{
"dirs": {
"a": [file1, file2, ...],
"b": [file3, ...]
...
},
...
}
Thing is, I need to create the contents of the object at dirs
as I go along. In other words, a
and b
don't necessarily exist at first.
The first time I need to create a new entry like this, it needs to be an array, with the entry as the first and only value:
{
"dirs": {
"a": [file1]
},
...
}
But subsequently I need to just append entries (such as file2
here) to the existing array:
{
"dirs": {
"a": [file1, file2]
},
...
}
The concept of autovivification came to mind; I first learned about this word and concept in my Perl days, and it's never left me (in fact a lot of of how I think in terms of complex data structures I learned back then).
Effectively I want to be able to push a new item, but make sure that the array exists first and create it if it doesn't. Investigating this led me to the family of path related functions path(path_expression)
, del(path_expression)
, getpath(PATHS)
, setpath(PATHS; VALUE)
and delpaths(PATHS)
.
Here's what I came up with, as a sort of "autovivification-push" (where the semantics of push are more from JavaScript's array.prototype.push():
def apush($pexp;$item):
setpath($pexp;(getpath($pexp) // []) + [$item])
;
Given that, then the following:
{
dirs: {
a: ["file1"]
}
}
| apush(path(.dirs.a);"file2")
| apush(path(.dirs.b);"file3")
| apush(path(.dirs.b);"file4")
produces this:
{
"dirs": {
"a": [
"file1",
"file2"
],
"b": [
"file3",
"file4"
]
}
}
The b
array is effectively autovivified when the first item (file3
) needs to be pushed.
Like I say, I may go off in another direction for this puzzle, but wanted to make a note of this apush
idea.
Update
Holy bananas batman. Mattias Wadman just replied to me on Mastodon with a much neater alternative, one that I should have realised sooner:
{
dirs: {
a: ["file1"]
}
}
| .dirs.a += ["file2"]
| .dirs.b += ["file3"]
| .dirs.b += ["file4"]
This results in the same JSON as above. This is a much more precise approach, that also, now I see it, is clearly more idiomatic. I had seen the +=
operator in the manual (in the Arithmetic update-assignment section) but looking at the description, I had applied only a narrow part of my brain and not seen that it might be usable beyond arithmetic operations! Of course! Thanks Mattias.