Repository: jsonata-js/jsonata
Pull request #363 (2019-09-05 15:01:50)

input arrays treated as single value unless explicitly mapped using $.
This is a proposed solution to bugs #332 and #316 which I think also addresses the comments that were discussed in #170 

This change only affects how top level JSON arrays are handled in the input data.  If you wish to explicitly map the JSONata expression to _each_ item in the input array, then the expression will need to explicitly declare that by starting it with `$.`  Otherwise the input array as a whole will be treated as the single context value of the expression.

I would argue that this is not a breaking change because the current behaviour is somewhat broken (hence the bug reports).  Although I have had to modify a couple of test results (edge cases, I think).

coveralls: 
[![Coverage Status](https://coveralls.io/builds/25548606/badge)](https://coveralls.io/builds/25548606)

Coverage remained the same at 100.0% when pulling **90242a7e32b6c9bbe6e80a6c4875626feecf8b99 on array_inputs** into **133ba24d24b275e3716be64268fe6466ae1bc3be on master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/25548606/badge)](https://coveralls.io/builds/25548606)

Coverage remained the same at 100.0% when pulling **90242a7e32b6c9bbe6e80a6c4875626feecf8b99 on array_inputs** into **133ba24d24b275e3716be64268fe6466ae1bc3be on master**.


Patch:
diff --git a/src/functions.js b/src/functions.js
index e0a7a3b..8896e25 100644
--- a/src/functions.js
+++ b/src/functions.js
@@ -1665,7 +1665,11 @@ const functions = (() => {
             for(var ii = 0; ii < input.length; ii++) {
                 var res =  lookup(input[ii], key);
                 if (typeof res !== 'undefined') {
-                    result.push(res);
+                    if (Array.isArray(res)) {
+                        result.push(...res);
+                    } else {
+                        result.push(res);
+                    }
                 }
             }
         } else if (input !== null && typeof input === 'object') {
diff --git a/src/jsonata.js b/src/jsonata.js
index 0ecef20..4a2621f 100644
--- a/src/jsonata.js
+++ b/src/jsonata.js
@@ -981,7 +981,7 @@ var jsonata = (function() {
         var result;
         // if the variable name is empty string, then it refers to context value
         if (expr.value === '') {
-            result = input;
+            result = input && input.outerWrapper ? input[0] : input;
         } else {
             result = environment.lookup(expr.value);
         }
@@ -1904,6 +1904,12 @@ var jsonata = (function() {
                 timestamp = new Date();
                 exec_env.timestamp = timestamp;
 
+                // if the input is a JSON array, then wrap it in a singleton sequence so it gets treated as a single input
+                if(Array.isArray(input) && !isSequence(input)) {
+                    input = createSequence(input);
+                    input.outerWrapper = true;
+                }
+
                 var result, it;
                 // if a callback function is supplied, then drive the generator in a promise chain
                 if(typeof callback === 'function') {


Test patch:
diff --git a/test/test-suite/groups/flattening/array-inputs.json b/test/test-suite/groups/flattening/array-inputs.json
new file mode 100644
index 0000000..b866fdd
--- /dev/null
+++ b/test/test-suite/groups/flattening/array-inputs.json
@@ -0,0 +1,72 @@
+[
+    {
+        "expr": "phone",
+        "data": [
+            {"phone": [{"number" : 0}]},
+            {"phone": [{"number" : 1}]},
+            {"phone": [{"number" : 2}]}
+        ],
+        "result": [
+            {"number":0},
+            {"number":1},
+            {"number":2}
+        ]
+    },
+    {
+        "expr": "phone[0]",
+        "data": [
+            {"phone": [{"number" : 0}]},
+            {"phone": [{"number" : 1}]},
+            {"phone": [{"number" : 2}]}
+        ],
+        "result": {"number":0}
+    },
+    {
+        "expr": "phone[0][]",
+        "data": [
+            {"phone": [{"number" : 0}]},
+            {"phone": [{"number" : 1}]},
+            {"phone": [{"number" : 2}]}
+        ],
+        "result": [
+            {"number":0}
+        ]
+    },
+    {
+        "expr": "phone[0].number",
+        "data": [
+            {"phone": [{"number" : 0}]},
+            {"phone": [{"number" : 1}]},
+            {"phone": [{"number" : 2}]}
+        ],
+        "result": 0
+    },
+    {
+        "expr": "(phone)[0].number",
+        "data": [
+            {"phone": [{"number" : 0}]},
+            {"phone": [{"number" : 1}]},
+            {"phone": [{"number" : 2}]}
+        ],
+        "result": 0
+    },
+    {
+        "expr": "$.phone[0].number",
+        "data": [
+            {"phone": [{"number" : 0}]},
+            {"phone": [{"number" : 1}]},
+            {"phone": [{"number" : 2}]}
+        ],
+        "result": [0,1,2]
+    },
+    {
+        "expr": "{'Hello':'World'}",
+        "data": [],
+        "result": {"Hello": "World"}
+    },
+    {
+        "expr": "$.{'Hello':'World'}",
+        "data": [],
+        "undefinedResult": true
+    }
+]
diff --git a/test/test-suite/groups/flattening/case034.json b/test/test-suite/groups/flattening/case034.json
index 5557f6d..c1cbd1f 100644
--- a/test/test-suite/groups/flattening/case034.json
+++ b/test/test-suite/groups/flattening/case034.json
@@ -31,7 +31,6 @@
         }
     ],
     "result": [
-        1,
-        3
+        1
     ]
-}
\ No newline at end of file
+}
diff --git a/test/test-suite/groups/flattening/case034a.json b/test/test-suite/groups/flattening/case034a.json
new file mode 100644
index 0000000..5f273e5
--- /dev/null
+++ b/test/test-suite/groups/flattening/case034a.json
@@ -0,0 +1,37 @@
+{
+    "expr": "$.a[0].b",
+    "data": [
+        {
+            "a": [
+                {
+                    "b": [
+                        1
+                    ]
+                },
+                {
+                    "b": [
+                        2
+                    ]
+                }
+            ]
+        },
+        {
+            "a": [
+                {
+                    "b": [
+                        3
+                    ]
+                },
+                {
+                    "b": [
+                        4
+                    ]
+                }
+            ]
+        }
+    ],
+    "result": [
+        1,
+        3
+    ]
+}
diff --git a/test/test-suite/groups/object-constructor/case023.json b/test/test-suite/groups/object-constructor/case023.json
index 1224945..5ce7f0b 100644
--- a/test/test-suite/groups/object-constructor/case023.json
+++ b/test/test-suite/groups/object-constructor/case023.json
@@ -1,5 +1,5 @@
 {
-    "expr": "{ type: $average(value), kind: $sum(value) } ",
+    "expr": "$.{ type: $average(value), kind: $sum(value) } ",
     "data": [
         { "type": "a", "kind": "a", "value": 0 },
         { "type": "a", "kind": "b", "value": 1 },
@@ -8,4 +8,4 @@
     ],
     "bindings": {},
     "code": "D1009"
-}
\ No newline at end of file
+}
diff --git a/test/test-suite/groups/object-constructor/case024.json b/test/test-suite/groups/object-constructor/case024.json
new file mode 100644
index 0000000..b5bb2ad
--- /dev/null
+++ b/test/test-suite/groups/object-constructor/case024.json
@@ -0,0 +1,11 @@
+{
+    "expr": "{ type: $average(value), kind: $sum(value) } ",
+    "data": [
+        { "type": "a", "kind": "a", "value": 0 },
+        { "type": "a", "kind": "b", "value": 1 },
+        { "type": "b", "kind": "a", "value": 2 },
+        { "type": "b", "kind": "b", "value": 3 }
+    ],
+    "bindings": {},
+    "code": "T1003"
+}