diff --git a/lib/types/keys.js b/lib/types/keys.js index a920afa4..62db4f95 100755 --- a/lib/types/keys.js +++ b/lib/types/keys.js @@ -602,7 +602,11 @@ module.exports = Any.extend({ if (schema.$_terms.keys) { const topo = new Topo.Sorter(); for (const child of schema.$_terms.keys) { - Common.tryWithPath(() => topo.add(child, { after: child.schema.$_rootReferences(), group: child.key }), child.key); + const keys = child.schema.$_terms.keys; + const after = child.schema.$_rootReferences() + .filter((ref) => !keys || !keys.some((local) => local.key === ref)); // Ignore references resolved internally by the child (e.g. a nested reference to a duplicated sibling key name) + + Common.tryWithPath(() => topo.add(child, { after, group: child.key }), child.key); } schema.$_terms.keys = new internals.Keys(...topo.nodes); diff --git a/test/types/object.js b/test/types/object.js index 88ae3bd6..18cbd491 100755 --- a/test/types/object.js +++ b/test/types/object.js @@ -1638,6 +1638,28 @@ describe('object', () => { [{ type: 'a', set: true, flag: true }, false, '"flag" must be [false]'] ]); }); + + it('builds keys referencing a duplicated sibling key name of a nested object', () => { + + const schema = Joi.object({ + id: Joi.number().required(), + command: Joi.object({ + command: Joi.string().valid('run', 'jump').required(), + params: Joi.alternatives().conditional('command', { + switch: [ + { is: 'run', then: Joi.object({ howFast: Joi.number().required() }) }, + { is: 'jump', then: Joi.object({ howHigh: Joi.number().required() }) } + ] + }) + }) + }); + + Helper.validate(schema, [ + [{ id: 1, command: { command: 'run', params: { howFast: 1 } } }, true], + [{ id: 1, command: { command: 'jump', params: { howHigh: 2 } } }, true], + [{ id: 1, command: { command: 'run', params: {} } }, false, '"command.params.howFast" is required'] + ]); + }); }); describe('length()', () => {