summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNicolas Williams <nico@cryptonector.com>2017-01-23 17:10:51 -0600
committerNicolas Williams <nico@cryptonector.com>2017-01-30 14:11:05 -0600
commite24af3c78e78a3aab05a2800d825d56f1d842b1b (patch)
treeefa76de9069bdbb00703002ca710acae8c3a2a90
parente82147bbb859413744ec9591065bf87d2b281ce4 (diff)
reduce: handle empty updates (fix #1313)
-rw-r--r--docs/content/3.manual/manual.yml8
-rw-r--r--jq.1.prebuilt235
-rw-r--r--src/compile.c17
-rw-r--r--tests/jq.test5
4 files changed, 196 insertions, 69 deletions
diff --git a/docs/content/3.manual/manual.yml b/docs/content/3.manual/manual.yml
index 4dd9d3ac..94aa2476 100644
--- a/docs/content/3.manual/manual.yml
+++ b/docs/content/3.manual/manual.yml
@@ -2413,10 +2413,18 @@ sections:
(2 as $item | . + $item) |
(1 as $item | . + $item)
+ If the reduction update expression outputs `empty` (that is,
+ no values), then the reduction state is left as-is. For
+ example, `reduce range(4) as $n ({}; if .==2 then empty else
+ .[$n|tostring] |= $n)` will produce `{"0":0,"1":1,"3":3}`.
+
examples:
- program: 'reduce .[] as $item (0; . + $item)'
input: '[10,2,5,3]'
output: ['20']
+ - program: 'reduce .[] as $n ([]; if $n%2==0 then empty else . + [$n] end)'
+ input: '[0,1,2,3,4,5]'
+ output: ['[1,3,5]']
- title: "`limit(n; exp)`"
body: |
diff --git a/jq.1.prebuilt b/jq.1.prebuilt
index 7ca3cba3..995b40e9 100644
--- a/jq.1.prebuilt
+++ b/jq.1.prebuilt
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
-.TH "JQ" "1" "July 2015" "" ""
+.TH "JQ" "1" "January 2017" "" ""
.
.SH "NAME"
\fBjq\fR \- Command\-line JSON processor
@@ -52,7 +52,7 @@ Output the jq version and exit with zero\.
\fB\-\-seq\fR:
.
.IP
-Use the \fBapplication/json\-seq\fR MIME type scheme for separating JSON texts in jq\'s input and output\. This means that an ASCII RS (record separator) character is printed before each value on output and an ASCII LF (line feed) is printed after every output\. Input JSON texts that fail to parse are ignored (but warned about), discarding all subsequent input until the next RS\. This more also parses the output of jq without the \fB\-\-seq\fR option\.
+Use the \fBapplication/json\-seq\fR MIME type scheme for separating JSON texts in jq\'s input and output\. This means that an ASCII RS (record separator) character is printed before each value on output and an ASCII LF (line feed) is printed after every output\. Input JSON texts that fail to parse are ignored (but warned about), discarding all subsequent input until the next RS\. This mode also parses the output of jq without the \fB\-\-seq\fR option\.
.
.IP "\(bu" 4
\fB\-\-stream\fR:
@@ -178,13 +178,13 @@ This option reads all the JSON texts in the named file and binds an array of the
Do not use\. Use \fB\-\-slurpfile\fR instead\.
.
.IP
-(This option is like \fB\-\-slurpfile\fR, but when the file has just one text, then that is used, else an array of texts is used as in \fB\-\-slurfile\fR\.)
+(This option is like \fB\-\-slurpfile\fR, but when the file has just one text, then that is used, else an array of texts is used as in \fB\-\-slurpfile\fR\.)
.
.IP "\(bu" 4
\fB\-\-run\-tests [filename]\fR:
.
.IP
-Runs the tests in the given file or standard input\. This must be the last option given and does not honor all preceding options\. The input consts of comment lines, empty lines, and program lines followed by one input line, as many lines of output as are expected (one per output), and a terminating empty line\. Compilation failure tests start with a line containing only "%%FAIL", then a line containing the program to compile, then a line containing an error message to compare to the actual\.
+Runs the tests in the given file or standard input\. This must be the last option given and does not honor all preceding options\. The input consists of comment lines, empty lines, and program lines followed by one input line, as many lines of output as are expected (one per output), and a terminating empty line\. Compilation failure tests start with a line containing only "%%FAIL", then a line containing the program to compile, then a line containing an error message to compare to the actual\.
.
.IP
Be warned that this option can change backwards\-incompatibly\.
@@ -277,7 +277,7 @@ The \fB\.[10:15]\fR syntax can be used to return a subarray of an array or subst
The \fB\.[2]\fR syntax can be used to return the element at the given index\. Negative indices are allowed, with \-1 referring to the last element, \-2 referring to the next to last element, and so on\.
.
.P
-The \fB\.foo\fR syntax only works for simply keys i\.e\. keys that are all alphanumeric characters\. \fB\.[<string>]\fR works with keys that contain special charactors such as colons and dots\. For example \fB\.["foo::bar"]\fR and \fB\.["foo\.bar"]\fR work while \fB\.foo::bar\fR and \fB\.foo\.bar\fR would not\.
+The \fB\.foo\fR syntax only works for simply keys i\.e\. keys that are all alphanumeric characters\. \fB\.[<string>]\fR works with keys that contain special characters such as colons and dots\. For example \fB\.["foo::bar"]\fR and \fB\.["foo\.bar"]\fR work while \fB\.foo::bar\fR and \fB\.foo\.bar\fR would not\.
.
.P
The \fB?\fR "operator" can also be used with the slice operator, as in \fB\.[10:15]?\fR, which outputs values where the inputs are slice\-able\.
@@ -573,7 +573,7 @@ jq \'{a: 1} + {b: 2} + {c: 3} + {a: 42}\'
.IP "" 0
.
.SS "Subtraction \- \-"
-As well as normal arithmetic subtraction on numbers, the \fB\-\fR operator can be used on arrays to remove all occurences of the second array\'s elements from the first array\.
+As well as normal arithmetic subtraction on numbers, the \fB\-\fR operator can be used on arrays to remove all occurrences of the second array\'s elements from the first array\.
.
.IP "" 4
.
@@ -592,13 +592,13 @@ jq \'\. \- ["xml", "yaml"]\'
.IP "" 0
.
.SS "Multiplication, division, modulo \- *, /, and %"
-These operators only work on numbers, and do the expected\.
+These infix operators behave as expected when given two numbers\. Division by zero raises an error\. \fBx % y\fR computes x modulo y\.
.
.P
-Multiplying a string by a number produces the concatenation of that string that many times\.
+Multiplying a string by a number produces the concatenation of that string that many times\. \fB"x" * 0\fR produces \fBnull\fR\.
.
.P
-Dividing a string by another splits the first using the second as separators\. Division by zero raises an error\.
+Dividing a string by another splits the first using the second as separators\.
.
.P
Multiplying two objects will merge them recursively: this works like addition but if both objects contain a value for the same key, and the values are objects, the two are merged with the same strategy\.
@@ -647,6 +647,21 @@ jq \'\.[] | length\' [[1,2], "string", {"a":2}, null] => 2, 6, 1, 0
.
.IP "" 0
.
+.SS "utf8bytelength"
+The builtin function \fButf8bytelength\fR outputs the number of bytes used to encode a string in UTF\-8\.
+.
+.IP "" 4
+.
+.nf
+
+jq \'utf8bytelength\'
+ "\eu03bc"
+=> 2
+.
+.fi
+.
+.IP "" 0
+.
.SS "keys, keys_unsorted"
The builtin function \fBkeys\fR, when given an object, returns its keys in an array\.
.
@@ -698,7 +713,7 @@ jq \'map(has(2))\'
.IP "" 0
.
.SS "in"
-The builtin function \fBin\fR returns the input key is in the given object, or the input index corresponds to an element in the given array\. It is, essentially, an inversed version of \fBhas\fR\.
+The builtin function \fBin\fR returns whether or not the input key is in the given object, or the input index corresponds to an element in the given array\. It is, essentially, an inversed version of \fBhas\fR\.
.
.IP "" 4
.
@@ -716,8 +731,33 @@ jq \'map(in([0,1]))\'
.
.IP "" 0
.
+.SS "map(x), map_values(x)"
+For any filter \fBx\fR, \fBmap(x)\fR will run that filter for each element of the input array, and return the outputs in a new array\. \fBmap(\.+1)\fR will increment each element of an array of numbers\.
+.
+.P
+Similarly, \fBmap_values(x)\fR will run that filter for each element, but it will return an object when an object is passed\.
+.
+.P
+\fBmap(x)\fR is equivalent to \fB[\.[] | x]\fR\. In fact, this is how it\'s defined\. Similarly, \fBmap_values(x)\fR is defined as \fB\.[] |= x\fR\.
+.
+.IP "" 4
+.
+.nf
+
+jq \'map(\.+1)\'
+ [1,2,3]
+=> [2,3,4]
+
+jq \'map_values(\.+1)\'
+ {"a": 1, "b": 2, "c": 3}
+=> {"a": 2, "b": 3, "c": 4}
+.
+.fi
+.
+.IP "" 0
+.
.SS "path(path_expression)"
-Outputs array representations of the given path expression in \fB\.\fR\. The outputs are arrays of strings (keys in objects0 and/or numbers (array indices\.
+Outputs array representations of the given path expression in \fB\.\fR\. The outputs are arrays of strings (object keys) and/or numbers (array indices)\.
.
.P
Path expressions are jq expressions like \fB\.a\fR, but also \fB\.[]\fR\. There are two types of path expressions: ones that can match exactly, and ones that cannot\. For example, \fB\.a\.b\.c\fR is an exact match path expression, while \fB\.a[]\.b\fR is not\.
@@ -766,11 +806,53 @@ jq \'del(\.[1, 2])\'
.
.IP "" 0
.
+.SS "getpath(PATHS)"
+The builtin function \fBgetpath\fR outputs the values in \fB\.\fR found at each path in \fBPATHS\fR\.
+.
+.IP "" 4
+.
+.nf
+
+jq \'getpath(["a","b"])\'
+ null
+=> null
+
+jq \'[getpath(["a","b"], ["a","c"])]\'
+ {"a":{"b":0, "c":1}}
+=> [0, 1]
+.
+.fi
+.
+.IP "" 0
+.
+.SS "setpath(PATHS; VALUE)"
+The builtin function \fBsetpath\fR sets the \fBPATHS\fR in \fB\.\fR to \fBVALUE\fR\.
+.
+.IP "" 4
+.
+.nf
+
+jq \'setpath(["a","b"]; 1)\'
+ null
+=> {"a": {"b": 1}}
+
+jq \'setpath(["a","b"]; 1)\'
+ {"a":{"b":0}}
+=> {"a": {"b": 1}}
+
+jq \'setpath([0,"a"]; 1)\'
+ null
+=> [{"a":1}]
+.
+.fi
+.
+.IP "" 0
+.
.SS "to_entries, from_entries, with_entries"
These functions convert between an object and an array of key\-value pairs\. If \fBto_entries\fR is passed an object, then for each \fBk: v\fR entry in the input, the output array includes \fB{"key": k, "value": v}\fR\.
.
.P
-\fBfrom_entries\fR does the opposite conversion, and \fBwith_entries(foo)\fR is a shorthand for \fBto_entries | map(foo) | from_entries\fR, useful for doing some operation to all keys and values of an object\. \fBfrom_entries\fR accepts key, Key, Name, value and Value as keys\.
+\fBfrom_entries\fR does the opposite conversion, and \fBwith_entries(foo)\fR is a shorthand for \fBto_entries | map(foo) | from_entries\fR, useful for doing some operation to all keys and values of an object\. \fBfrom_entries\fR accepts key, Key, name, Name, value and Value as keys\.
.
.IP "" 4
.
@@ -869,31 +951,6 @@ jq \'try error("\e($__loc__)") catch \.\'
.
.IP "" 0
.
-.SS "map(x), map_values(x)"
-For any filter \fBx\fR, \fBmap(x)\fR will run that filter for each element of the input array, and produce the outputs a new array\. \fBmap(\.+1)\fR will increment each element of an array of numbers\.
-.
-.P
-Similarly, \fBmap_values(x)\fR will run that filter for each element, but it will return an object when an object is passed\.
-.
-.P
-\fBmap(x)\fR is equivalent to \fB[\.[] | x]\fR\. In fact, this is how it\'s defined\. Similarly, \fBmap_values(x)\fR is defined as \fB\.[] |= x\fR\.
-.
-.IP "" 4
-.
-.nf
-
-jq \'map(\.+1)\'
- [1,2,3]
-=> [2,3,4]
-
-jq \'map_values(\.+1)\'
- {"a": 1, "b": 2, "c": 3}
-=> {"a": 2, "b": 3, "c": 4}
-.
-.fi
-.
-.IP "" 0
-.
.SS "paths, paths(node_filter), leaf_paths"
\fBpaths\fR outputs the paths to all the elements in its input (except it does not output the empty list, representing \. itself)\.
.
@@ -946,7 +1003,7 @@ jq \'add\'
.IP "" 0
.
.SS "any, any(condition), any(generator; condition)"
-The filter \fBany\fR takes as input an array of boolean values, and produces \fBtrue\fR as output if any of the the elements of the array is \fBtrue\fR\.
+The filter \fBany\fR takes as input an array of boolean values, and produces \fBtrue\fR as output if any of the elements of the array are \fBtrue\fR\.
.
.P
If the input is an empty array, \fBany\fR returns \fBfalse\fR\.
@@ -978,7 +1035,7 @@ jq \'any\'
.IP "" 0
.
.SS "all, all(condition), all(generator; condition)"
-The filter \fBall\fR takes as input an array of boolean values, and produces \fBtrue\fR as output if all of the the elements of the array are \fBtrue\fR\.
+The filter \fBall\fR takes as input an array of boolean values, and produces \fBtrue\fR as output if all of the elements of the array are \fBtrue\fR\.
.
.P
The \fBall(condition)\fR form applies the given condition to the elements of the input array\.
@@ -1009,7 +1066,7 @@ jq \'all\'
.
.IP "" 0
.
-.SS "[Requires 1\.5] flatten, flatten(depth)"
+.SS "flatten, flatten(depth)"
The filter \fBflatten\fR takes as input an array of nested arrays, and produces a flat array in which all arrays inside the original array have been recursively replaced by their values\. You can pass an argument to it to specify how many levels of nesting to flatten\.
.
.P
@@ -1528,7 +1585,7 @@ jq \'implode\'
.
.IP "" 0
.
-.SS "split"
+.SS "split(str)"
Splits an input string on the separator argument\.
.
.IP "" 4
@@ -1546,6 +1603,9 @@ jq \'split(", ")\'
.SS "join(str)"
Joins the array of elements given as input, using the argument as separator\. It is the inverse of \fBsplit\fR: that is, running \fBsplit("foo") | join("foo")\fR over any input string returns said input string\.
.
+.P
+Numbers and booleans in the input are converted to strings\. Null values are treated as empty strings\. Arrays and objects in the input are not supported\.
+.
.IP "" 4
.
.nf
@@ -1553,6 +1613,10 @@ Joins the array of elements given as input, using the argument as separator\. It
jq \'join(", ")\'
["a","b,c,d","e"]
=> "a, b,c,d, e"
+
+jq \'join(" ")\'
+ ["a",1,2\.3,true,null,false]
+=> "a 1 2\.3 true false"
.
.fi
.
@@ -1664,6 +1728,25 @@ jq \'recurse(\. * \.; \. < 20)\'
.
.IP "" 0
.
+.SS "walk(f)"
+The \fBwalk(f)\fR function applies f recursively to every component of the input entity\. When an array is encountered, f is first applied to its elements and then to the array itself; when an object is encountered, f is first applied to all the values and then to the object\. In practice, f will usually test the type of its input, as illustrated in the following examples\. The first example highlights the usefulness of processing the elements of an array of arrays before processing the array itself\. The second example shows how all the keys of all the objects within the input can be considered for alteration\.
+.
+.IP "" 4
+.
+.nf
+
+jq \'walk(if type == "array" then sort else \. end)\'
+ [[4, 1, 7], [8, 5, 2], [3, 6, 9]]
+=> [[1,4,7],[2,5,8],[3,6,9]]
+
+jq \'walk( if type == "object" then with_entries( \.key |= sub( "^_+"; "") ) else \. end )\'
+ [ { "_a": { "__b": 2 } } ]
+=> [{"a":{"b":2}}]
+.
+.fi
+.
+.IP "" 0
+.
.SS "\.\."
Short\-hand for \fBrecurse\fR without arguments\. This is intended to resemble the XPath \fB//\fR operator\. Note that \fB\.\.a\fR does not work; use \fB\.\.|a\fR instead\. In the example below we use \fB\.\.|\.a?\fR to find all the values of object keys "a" in any object found "below" \fB\.\fR\.
.
@@ -1783,7 +1866,7 @@ Calls \fBtostring\fR, see that function for details\.
\fB@json\fR:
.
.IP
-Serialises the input as JSON\.
+Serializes the input as JSON\.
.
.TP
\fB@html\fR:
@@ -1828,7 +1911,7 @@ This syntax can be combined with string interpolation in a useful way\. You can
.
.nf
-@uri "http://www\.google\.com/search?q=\e(\.search)"
+@uri "https://www\.google\.com/search?q=\e(\.search)"
.
.fi
.
@@ -1841,7 +1924,7 @@ will produce the following output for the input \fB{"search":"what is jq?"}\fR:
.
.nf
-"http://www\.google\.com/search?q=what%20is%20jq%3F"
+"https://www\.google\.com/search?q=what%20is%20jq%3F"
.
.fi
.
@@ -1949,7 +2032,7 @@ jq \'\.[] == 1\'
Checking for false or null is a simpler notion of "truthiness" than is found in Javascript or Python, but it means that you\'ll sometimes have to be more explicit about the condition you want: you can\'t test whether, e\.g\. a string is empty using \fBif \.name then A else B end\fR, you\'ll need something more like \fBif (\.name | length) > 0 then A else B end\fR instead\.
.
.P
-If the condition A produces multiple results, it is considered "true" if any of those results is not false or null\. If it produces zero results, it\'s considered false\.
+If the condition \fBA\fR produces multiple results, then \fBB\fR is evaluated once for each result that is not false or null, and \fBC\fR is evaluated once for each false or null\.
.
.P
More cases can be added to an if using \fBelif A then B\fR syntax\.
@@ -1965,18 +2048,7 @@ jq \'if \. == 0 then
.IP "" 0
.
.P
-"zero" elif \. == 1 then "one" else "many" end\'
-.
-.IP "" 4
-.
-.nf
-
- 2
-=> "many"
-.
-.fi
-.
-.IP "" 0
+"zero" elif \. == 1 then "one" else "many" end\' 2 => "many"
.
.SS ">, >=, <=, <"
The comparison operators \fB>\fR, \fB>=\fR, \fB<=\fR, \fB<\fR return whether their left argument is greater than, greater than or equal to, less than or equal to or less than their right argument (respectively)\.
@@ -2232,7 +2304,7 @@ jq \-n \'("test", "TEst", "teST", "TEST") | test( "(?i)te(?\-i)st" )\'
.P
evaluates to: true, true, false, false\.
.
-.SS "[Requires 1\.5] test(val), test(regex; flags)"
+.SS "test(val), test(regex; flags)"
Like \fBmatch\fR, but does not return match objects, only \fBtrue\fR or \fBfalse\fR for whether or not the regex matches the input\.
.
.IP "" 4
@@ -2251,7 +2323,7 @@ jq \'\.[] | test("a b c # spaces are ignored"; "ix")\'
.
.IP "" 0
.
-.SS "[Requires 1\.5] match(val), match(regex; flags)"
+.SS "match(val), match(regex; flags)"
\fBmatch\fR outputs an object for each match it finds\. Matches have the following fields:
.
.IP "\(bu" 4
@@ -2316,7 +2388,7 @@ jq \'[ match("\."; "g")] | length\'
.
.IP "" 0
.
-.SS "[Requires 1\.5] capture(val), capture(regex; flags)"
+.SS "capture(val), capture(regex; flags)"
Collects the named captures in a JSON object, with the name of each capture as the key, and the matched string as the corresponding value\.
.
.IP "" 4
@@ -2331,19 +2403,19 @@ jq \'capture("(?<a>[a\-z]+)\-(?<n>[0\-9]+)")\'
.
.IP "" 0
.
-.SS "[Requires 1\.5] scan(regex), scan(regex; flags)"
+.SS "scan(regex), scan(regex; flags)"
Emit a stream of the non\-overlapping substrings of the input that match the regex in accordance with the flags, if any have been specified\. If there is no match, the stream is empty\. To capture all the matches for each input string, use the idiom \fB[ expr ]\fR, e\.g\. \fB[ scan(regex) ]\fR\.
.
.SS "split(regex; flags)"
For backwards compatibility, \fBsplit\fR splits on a string, not a regex\.
.
-.SS "[Requires 1\.5] splits(regex), splits(regex; flags)"
+.SS "splits(regex), splits(regex; flags)"
These provide the same results as their \fBsplit\fR counterparts, but as a stream instead of an array\.
.
-.SS "[Requires 1\.5] sub(regex; tostring) sub(regex; string; flags)"
+.SS "sub(regex; tostring) sub(regex; string; flags)"
Emit the string obtained by replacing the first match of regex in the input string with \fBtostring\fR, after interpolation\. \fBtostring\fR should be a jq string, and may contain references to named captures\. The named captures are, in effect, presented as a JSON object (as constructed by \fBcapture\fR) to \fBtostring\fR, so a reference to a captured variable named "x" would take the form: "(\.x)"\.
.
-.SS "[Requires 1\.5] gsub(regex; string), gsub(regex; string; flags)"
+.SS "gsub(regex; string), gsub(regex; string; flags)"
\fBgsub\fR is like \fBsub\fR but all the non\-overlapping occurrences of the regex are replaced by the string, after interpolation\.
.
.SH "ADVANCED FEATURES"
@@ -2624,10 +2696,25 @@ For each result that \fB\.[]\fR produces, \fB\. + $item\fR is run to accumulate
0 | (3 as $item | \. + $item) |
(2 as $item | \. + $item) |
(1 as $item | \. + $item)
+.
+.fi
+.
+.IP "" 0
+.
+.P
+If the reduction update expression outputs \fBempty\fR (that is, no values), then the reduction state is left as\-is\. For example, \fBreduce range(4) as $n ({}; if \.==2 then empty else \.[$n|tostring] |= $n)\fR will produce \fB{"0":0,"1":1,"3":3}\fR\.
+.
+.IP "" 4
+.
+.nf
jq \'reduce \.[] as $item (0; \. + $item)\'
[10,2,5,3]
=> 20
+
+jq \'reduce \.[] as $n ([]; if $n%2==0 then empty else \. + [$n] end)\'
+ [0,1,2,3,4,5]
+=> [1,3,5]
.
.fi
.
@@ -2709,7 +2796,7 @@ jq \'[foreach \.[] as $item ([[],[]]; if $item == null then [[],\.[0]] else [(\.
As described above, \fBrecurse\fR uses recursion, and any jq function can be recursive\. The \fBwhile\fR builtin is also implemented in terms of recursion\.
.
.P
-Tail calls are optmized whenever the expression to the left of the recursive call outputs its last value\. In practice this means that the expression to the left of the recursive call should not produce more than one output for each input\.
+Tail calls are optimized whenever the expression to the left of the recursive call outputs its last value\. In practice this means that the expression to the left of the recursive call should not produce more than one output for each input\.
.
.P
For example:
@@ -2805,7 +2892,7 @@ However, streaming isn\'t easy to deal with as the jq program will have \fB[<pat
Several builtins are provided to make handling streams easier\.
.
.P
-The examples below use the the streamed form of \fB[0,[1]]\fR, which is \fB[[0],1],[[1,0],2],[[1,0]],[[1]])]\fR\.
+The examples below use the streamed form of \fB[0,[1]]\fR, which is \fB[[0],0],[[1,0],1],[[1,0]],[[1]]\fR\.
.
.P
Streaming forms include \fB[<path>, <leaf\-value>]\fR (to indicate any scalar value, empty array, or empty object), and \fB[<path>]\fR (to indicate the end of an array or object)\. Future versions of jq run with \fB\-\-stream\fR and \fB\-seq\fR may output additional forms such as \fB["error message"]\fR when an input text fails to parse\.
@@ -2861,6 +2948,9 @@ Assignment works a little differently in jq than in most programming languages\.
.P
If an object has two fields which are arrays, \fB\.foo\fR and \fB\.bar\fR, and you append something to \fB\.foo\fR, then \fB\.bar\fR will not get bigger\. Even if you\'ve just set \fB\.bar = \.foo\fR\. If you\'re used to programming in languages like Python, Java, Ruby, Javascript, etc\. then you can think of it as though jq does a full deep copy of every object before it does the assignment (for performance, it doesn\'t actually do that, but that\'s the general idea)\.
.
+.P
+All the assignment operators in jq have path expressions on the left\-hand side\.
+.
.SS "="
The filter \fB\.foo = 1\fR will take as input an object and produce as output an object with the "foo" field set to 1\. There is no notion of "modifying" or "changing" something in jq \- all jq values are immutable\. For instance,
.
@@ -2874,7 +2964,13 @@ will not have the side\-effect of setting \.bar\.baz to be set to 1, as the simi
This means that it\'s impossible to build circular values in jq (such as an array whose first element is itself)\. This is quite intentional, and ensures that anything a jq program can produce can be represented in JSON\.
.
.P
-Note that the left\-hand side of \'=\' refers to a value in \fB\.\fR\. Thus \fB$var\.foo = 1\fR won\'t work as expected; use \fB$var | \.foo = 1\fR instead\.
+Note that the left\-hand side of \'=\' refers to a value in \fB\.\fR\. Thus \fB$var\.foo = 1\fR won\'t work as expected (\fB$var\.foo\fR is not a valid or useful path expression in \fB\.\fR); use \fB$var | \.foo = 1\fR instead\.
+.
+.P
+If the right\-hand side of \'=\' produces multiple values, then for each such value jq will set the paths on the left\-hand side to the value and then it will output the modified \fB\.\fR\. For example, \fB(\.a,\.b)=range(2)\fR outputs \fB{"a":0,"b":0}\fR, then \fB{"a":1,"b":1}\fR\. The "update" assignment forms (see below) do not do this\.
+.
+.P
+Note too that \fB\.a,\.b=0\fR does not set \fB\.a\fR and \fB\.b\fR, but \fB(\.a,\.b)=0\fR sets both\.
.
.SS "|="
As well as the assignment operator \'=\', jq provides the "update" operator \'|=\', which takes a filter on the right\-hand side and works out the new value for the property of \fB\.\fR being assigned to by running the old value through this expression\. For instance, \.foo |= \.+1 will build an object with the "foo" field set to the input\'s "foo" plus 1\.
@@ -2895,7 +2991,10 @@ The former will set the "a" field of the input to the "b" field of the input, an
The left\-hand side can be any general path expression; see \fBpath()\fR\.
.
.P
-Note that the left\-hand side of \'|=\' refers to a value in \fB\.\fR\. Thus \fB$var\.foo |= \. + 1\fR won\'t work as expected; use \fB$var | \.foo |= \. + 1\fR instead\.
+Note that the left\-hand side of \'|=\' refers to a value in \fB\.\fR\. Thus \fB$var\.foo |= \. + 1\fR won\'t work as expected (\fB$var\.foo\fR is not a valid or useful path expression in \fB\.\fR); use \fB$var | \.foo |= \. + 1\fR instead\.
+.
+.P
+If the right\-hand side outputs multiple values, only the last one will be used\.
.
.IP "" 4
.
diff --git a/src/compile.c b/src/compile.c
index 4276d3bf..a5c75c96 100644
--- a/src/compile.c
+++ b/src/compile.c
@@ -704,12 +704,27 @@ static block bind_matcher(block matcher, block body) {
block gen_reduce(block source, block matcher, block init, block body) {
block res_var = gen_op_var_fresh(STOREV, "reduce");
+ block update_var = gen_op_bound(STOREV, res_var);
+ block jmp = gen_op_target(JUMP, body);
block loop = BLOCK(gen_op_simple(DUPN),
source,
bind_matcher(matcher,
BLOCK(gen_op_bound(LOADVN, res_var),
+ /*
+ * We fork to the body, jump to
+ * the STOREV. This means that
+ * if body produces no results
+ * (i.e., it just does empty)
+ * then we keep the current
+ * reduction state as-is.
+ *
+ * To break out of a
+ * reduction... use break.
+ */
+ gen_op_target(FORK, jmp),
+ jmp,
body,
- gen_op_bound(STOREV, res_var))),
+ update_var)),
gen_op_simple(BACKTRACK));
return BLOCK(gen_op_simple(DUP),
init,
diff --git a/tests/jq.test b/tests/jq.test
index ad4a51ed..fc92d35b 100644
--- a/tests/jq.test
+++ b/tests/jq.test
@@ -686,6 +686,11 @@ reduce [[1,2,10], [3,4,10]][] as [$i,$j] (0; . + $i * $j)
null
14
+# Test fix for #1313 (reduce should handle empty updates)
+reduce range(5) as $n ([]; select($n%2 == 1) | . + [$n])
+null
+[1,3]
+
. as $dot|any($dot[];not)
[1,2,3,4,true,false,1,2,3,4,5]
true