Skip to content

only merge changed properties into value during alternative match merge #2581

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 87 additions & 4 deletions lib/types/alternatives.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
'use strict';

const Assert = require('@hapi/hoek/lib/assert');
const Merge = require('@hapi/hoek/lib/merge');
const Clone = require('@hapi/hoek/lib/clone');
const Utils = require('@hapi/hoek/lib/utils');

const Any = require('./any');
const Common = require('../common');
Expand All @@ -12,6 +13,80 @@ const Ref = require('../ref');

const internals = {};

// note: placed here purely for illustration purposes. will move to appropriate location before merging
// merges source properties into target, but only when they differ from the value in original
const differenceMerge = function (target, source, original, options) {

Assert(target && typeof target === 'object', 'Invalid target value: must be an object');
Assert(source === null || source === undefined || typeof source === 'object', 'Invalid source value: must be null, undefined, or an object');
Assert(original && typeof original === 'object', 'Invalid original value: must be an object');

if (!source) {
return target;
}

options = Object.assign({ nullOverride: true, mergeArrays: true }, options);

if (Array.isArray(source)) {
Assert(Array.isArray(target), 'Cannot merge array onto an object');
if (!options.mergeArrays) {
target.length = 0; // Must not change target assignment
}

for (let i = 0; i < source.length; ++i) {
target.push(Clone(source[i], { symbols: options.symbols }));
}

return target;
}

const keys = Utils.keys(source, options);
for (let i = 0; i < keys.length; ++i) {
const key = keys[i];
if (key === '__proto__' ||
!Object.prototype.propertyIsEnumerable.call(source, key)) {

continue;
}

const value = source[key];
if (value &&
typeof value === 'object') {

if (target[key] === value) {
continue; // Can occur for shallow merges
}

if (!target[key] ||
typeof target[key] !== 'object' ||
(Array.isArray(target[key]) !== Array.isArray(value)) ||
value instanceof Date ||
(Buffer && Buffer.isBuffer(value)) || // $lab:coverage:ignore$
value instanceof RegExp) {
if (value !== original[key]) {
target[key] = Clone(value, { symbols: options.symbols });
}
}
else {
differenceMerge(target[key], value, original[key], options);
}
}
else {
if (value !== original[key]) {
if (value !== null &&
value !== undefined) { // Explicit to preserve empty strings
target[key] = value;
}
else if (options.nullOverride) {
target[key] = value;
}
}
}
}

return target;

};

module.exports = Any.extend({

Expand Down Expand Up @@ -45,6 +120,7 @@ module.exports = Any.extend({
// Match all or one

if (schema._flags.match) {
const valueClone = Clone(value);
const matched = [];

for (let i = 0; i < schema.$_terms.matches.length; ++i) {
Expand Down Expand Up @@ -74,7 +150,14 @@ module.exports = Any.extend({
}

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] };

if (allobj) {
// add original value to set prior to merging any changed properties from matched subschemas
matched.unshift(valueClone);
return { value: matched.reduce((acc, v) => differenceMerge(acc, v, value, { mergeArrays: false })) };
}

return { value: matched[matched.length - 1] };
}

// Match any
Expand Down Expand Up @@ -138,7 +221,7 @@ module.exports = Any.extend({
const conditions = match.is ? [match] : match.switch;
for (const item of conditions) {
if (item.then &&
item.otherwise) {
item.otherwise) {

obj.$_setFlag('_endedSwitch', true, { clone: false });
break;
Expand Down Expand Up @@ -200,7 +283,7 @@ module.exports = Any.extend({
const each = (item) => {

if (Common.isSchema(item) &&
item.type === 'array') {
item.type === 'array') {

schema.$_setFlag('_arrayItems', true, { clone: false });
}
Expand Down