Repository: hapijs/joi (JavaScript)

Issue:
defaults in alternatives with a match mode set are not returned
#### Support plan

* *is this issue currently blocking your project?* (yes/no): yes
* *is this issue affecting a production system?* (yes/no): no

#### Context

* *node version*: 12.x
* *module version with issue*:  17.3.0
* *last module version without issue*: n/a 
* *environment* (e.g. node, browser, native): node
* *used with* (e.g. hapi application, another framework, standalone, ...): standalone

#### What are you trying to achieve or the steps to reproduce?

Properties with defaults should be applied, even if they're part of an alternative subschema with a match mode.

```js
const joi = require('joi')
const s = joi.alternatives().try(
  joi.object({ 
    lol: joi.string().default('rofl')
  }).unknown(), 
  joi.object({ 
    foo: joi.string().default('bar'), 
    baz: joi.string() 
  })
).match('all')
const { value, error } = s.validate({ baz: 'bing' })
```

#### What was the result you got?

```js
error === undefined
value === { baz: 'bing' }
```

#### What result did you expect?

```js
error === undefined
value === { lol: 'rofl', foo: 'bar', baz: 'bing' }
```



Discussion:
Could you take a look at this when you have a chance @hueniverse ?

Have some work that depends on this and would appreciate any help getting this into a release so I can get off my fork.



Patch:
diff --git a/lib/types/alternatives.js b/lib/types/alternatives.js
index 940c66974..875f2b090 100755
--- a/lib/types/alternatives.js
+++ b/lib/types/alternatives.js
@@ -1,6 +1,7 @@
 'use strict';
 
 const Assert = require('@hapi/hoek/lib/assert');
+const Merge = require('@hapi/hoek/lib/merge');
 
 const Any = require('./any');
 const Common = require('../common');
@@ -44,8 +45,7 @@ module.exports = Any.extend({
         // Match all or one
 
         if (schema._flags.match) {
-            let hits = 0;
-            let matched;
+            const matched = [];
 
             for (let i = 0; i < schema.$_terms.matches.length; ++i) {
                 const item = schema.$_terms.matches[i];
@@ -54,23 +54,27 @@ module.exports = Any.extend({
 
                 const result = item.schema.$_validate(value, localState, prefs);
                 if (!result.errors) {
-                    ++hits;
-                    matched = result.value;
+                    matched.push(result.value);
                 }
                 else {
                     localState.restore();
                 }
             }
 
-            if (!hits) {
+            if (matched.length === 0) {
                 return { errors: error('alternatives.any') };
             }
 
             if (schema._flags.match === 'one') {
-                return hits === 1 ? { value: matched } : { errors: error('alternatives.one') };
+                return matched.length === 1 ? { value: matched[0] } : { errors: error('alternatives.one') };
             }
 
-            return hits === schema.$_terms.matches.length ? { value } : { errors: error('alternatives.all') };
+            if (matched.length !== schema.$_terms.matches.length) {
+                return { errors: error('alternatives.all') };
+            }
+
+            const allobj = schema.$_terms.matches.reduce((acc, v) => acc && v.schema.type === 'object', true);
+            return allobj ? { value: matched.reduce((acc, v) => Merge(acc, v, { mergeArrays: false })) } : { value: matched[matched.length - 1] };
         }
 
         // Match any


Test patch:
diff --git a/test/types/alternatives.js b/test/types/alternatives.js
index c610da9ee..25fcf95cf 100755
--- a/test/types/alternatives.js
+++ b/test/types/alternatives.js
@@ -1451,6 +1451,28 @@ describe('alternatives', () => {
             ]);
         });
 
+        it('merges return values when matching all objects', () => {
+
+            const schema = Joi.alternatives([
+                Joi.object({ foo: Joi.string().default('bar') }).unknown(),
+                Joi.object({
+                    baz: Joi.string().default('buz'),
+                    lol: Joi.string()
+                })
+            ]).match('all');
+
+            Helper.validate(schema, [
+                [{}, true, { foo: 'bar', baz: 'buz' }],
+                [{ lol: 'rofl' }, true, { foo: 'bar', baz: 'buz', lol: 'rofl' }],
+                [{ lol: 5 }, false, {
+                    message: '"value" does not match all of the required types',
+                    path: [],
+                    type: 'alternatives.all',
+                    context: { label: 'value', value: { lol: 5 } }
+                }]
+            ]);
+        });
+
         it('errors on mix with conditional', () => {
 
             expect(() => Joi.alternatives().match('all').conditional('$a', { is: true, then: false })).to.throw('Cannot combine match mode all with conditional rule');