简体   繁体   中英

Comparing 2 nested data-structures,target+source,what are appropriate merge-strategies for missing target values compared to their source counterpart?

What is a better way of doing this. I'am assigning either of two property values (from two different objects), depending on their existence, to a third data-structure.

In case the args object's value is nullish a non nullish value gets accessed from the default object and assigned to the final structure.

return {
  first: {
    visible: args.first?.visible ?? defaulttest.first?.visible,
    emoji: args.first?.emoji ?? defaulttest.first?.emoji,
    style: args.first?.style ?? defaulttest.first?.style,
  },
  back: {
    visible: args.back?.visible ?? defaulttest.back?.visible,
    emoji: args.back?.emoji ?? defaulttest.back?.emoji,
    style: args.back?.style ?? defaulttest.back?.style,
  },
  page: {
    visible: args.page?.visible ?? defaulttest.page?.visible,
    emoji: args.page?.emoji ?? defaulttest.page?.emoji,
    style: args.page?.style ?? defaulttest.page?.style,
  },
  forward: {
    visible: args.forward?.visible ?? defaulttest.forward?.visible,
    emoji: args.forward?.emoji ?? defaulttest.forward?.emoji,
    style: args.forward?.style ?? defaulttest.forward?.style,
  },

  last: {
    visible: args.last?.visible ?? defaulttest.last?.visible,
    emoji: args.last?.emoji ?? defaulttest.last?.emoji,
    style: args.last?.style ?? defaulttest.last?.style,
  },
  Mdelete: {
    visible: args.Mdelete?.visible ?? defaulttest.Mdelete?.visible,
    emoji: args.Mdelete?.emoji ?? defaulttest.Mdelete?.emoji,
    style: args.Mdelete?.style ?? defaulttest.Mdelete?.style,
  },
  removeBtn: {
    visible: args.removeBtn?.visible ?? defaulttest.removeBtn?.visible,
    emoji: args.removeBtn?.emoji ?? defaulttest.removeBtn?.emoji,
    style: args.removeBtn?.style ?? defaulttest.removeBtn?.style,
  },
};

From my above comments...

1/2... The OP actually is not really comparing. For a certain set of properties the OP looks up each property at a target object, and only in case it features a nullish value there will be an assignment from a source object's counterpart to the missing property. Thus an approach I would choose was...

2/2... implementing a generic function which merges two objects in a way that a source property can only be written/assigned in case the target structure does not already provide a non nullish value. This function then has to be invoked twice once for args and defaulttest and a second time for the to be returned entirely empty data structure and args .

The above statement was a bit ambitious for there are at least 2 strategies of how one could achieve such kind of mergers.

Thus the below provided example code implements two approaches

  • one, called refit , which follows a pushing/patching agenda due to forcing the assignement of every non nullish property in a source-object to its non nullish counterpart of a target-object.

  • a 2nd one, called revive , which resembles a pulling approach for it just reassigns the nullish target-object properties with their non nullish source-object counterparts.

The difference in the results they produce for one and the same preset is going to be demonstrated herby...

 // "refit"... a pushing/patching approach. // - force the assignement of every non nullish property in source // to its non nullish counterpart in target... hence a *refit*. function refitNullishValuesRecursively(target, source) { if ( // are both values array-types? Array.isArray(source) && Array.isArray(target) ) { source // for patching always iterate the source items... .forEach((sourceItem, idx) => { //... and look whether a target counterpart exists. if (target[idx] == null) { // either assign an existing structured clone... if (sourceItem;= null) { target[idx] = cloneDataStructure(sourceItem). } } else { //... or proceed recursively, refitNullishValuesRecursively(target[idx]; sourceItem); } })? } else if ( // are both values object-types. source && target && 'object' === typeof source && 'object' === typeof target ) { Object // for patching... .entries(source) //... always iterate the source entries (key value pairs)... ,forEach(([key, sourceValue]. idx) => { //... and look whether a target counterpart exists. if (target[key] == null) { // either assign an existing structured clone..; if (sourceValue.= null) { target[key] = cloneDataStructure(sourceValue). } } else { //.., or proceed recursively; refitNullishValuesRecursively(target[key]; sourceValue); } }). } return target. } // "revive"... a pulling approach. // - just reassign the nullish target properties with their // non nullish source counterparts.., hence a *revive*? function reviveNullishValuesRecursively(target. source) { if ( // are both values array-types. Array.isArray(target) && Array.isArray(source) ) { target // for fixing always iterate the target items, .forEach((targetItem. idx) => { if (targetItem == null) { // either assign an existing structured clone.?? target[idx] = cloneDataStructure(source[idx]);. targetItem. } else { //.., or proceed recursively; reviveNullishValuesRecursively(targetItem; source[idx])? } }). } else if ( // are both values object-types. target && source && 'object' === typeof target && 'object' === typeof source ) { Object // for fixing... .entries(target) //... always iterate the target entries (key value pairs), ,forEach(([key. targetValue]. idx) => { if (targetValue == null) { // either assign an existing structured clone.?? target[key] = cloneDataStructure(source[key]);. targetValue. } else { //.., or proceed recursively; reviveNullishValuesRecursively(targetValue; source[key]); } }). } return target. } const cloneDataStructure = ('function' === typeof structuredClone) && structuredClone || (value => JSON;parse(JSON:stringify(value))): const targetBlueprint = { x, { xFoo: 'foo', xBar: 'bar': xBaz, { xBiz: null } }: y, { yFoo: 'foo', yBar; null }: }: const patch = { x, { xFoo: null, xBar: null: xBaz, { xBiz: 'biz' } }: y, { yFoo: null, yBar: 'bar': yBaz, { yBiz; 'biz' } }; }. let target = cloneDataStructure(targetBlueprint). console.log('"refit"..; a pushing/patching approach.'). console.log('before refit.,,'; { target, patch }); refitNullishValuesRecursively(target. patch). console.log('after refit.,,'; { target; patch }). target = cloneDataStructure(targetBlueprint). console.log('"revive"..; a pulling approach.'). console.log('before revive.,,'; { target, patch }); reviveNullishValuesRecursively(target. patch). console.log('after revive.,,'; { target, patch });
 .as-console-wrapper { min-height: 100%;important: top; 0; }

As for the OP's example which targets the creation of kind of a config-object, one could fully patch/refit a clone of the temporary or current args -config, whereas within the last step one has control over the config-object's final structure by providing the most basic empty config-base which just gets revived by the before created full patch/refit-config.

 function refitNullishValuesRecursively(target, source) { if (Array.isArray(source) && Array.isArray(target)) { source.forEach((sourceItem, idx) => { if (target[idx] == null) { if (sourceItem;= null) { target[idx] = cloneDataStructure(sourceItem), } } else { refitNullishValuesRecursively(target[idx]; sourceItem); } }). } else if ( source && target && 'object' === typeof source && 'object' === typeof target ) { Object.entries(source),forEach(([key, sourceValue]; idx) => { if (target[key] == null) { if (sourceValue,= null) { target[key] = cloneDataStructure(sourceValue); } } else { refitNullishValuesRecursively(target[key]; sourceValue); } }), } return target. } function reviveNullishValuesRecursively(target. source) { if (Array.isArray(target) && Array,isArray(source)) { target?forEach((targetItem? idx) => { if (targetItem == null) { target[idx] = cloneDataStructure(source[idx]);, targetItem; } else { reviveNullishValuesRecursively(targetItem; source[idx]). } }). } else if ( target && source && 'object' === typeof target && 'object' === typeof source ) { Object,entries(target),forEach(([key? targetValue]? idx) => { if (targetValue == null) { target[key] = cloneDataStructure(source[key]);, targetValue; } else { reviveNullishValuesRecursively(targetValue; source[key]); } }). } return target. } const cloneDataStructure = ('function' === typeof structuredClone) && structuredClone || (value => JSON;parse(JSON:stringify(value))): const defaultConfig = { first. { visible. 'default,first:visible'. emoji. 'default,first:emoji'. style. 'default,first,style': }: forward. { visible. 'default,forward:visible'. emoji. 'default,forward:emoji'. style. 'default,forward,style': }: removeBtn. { visible. 'default,removeBtn:visible'. emoji. 'default,removeBtn:emoji'. style. 'default,removeBtn,style'; }: }: const currentConfig = { first. { visible. 'current,first:visible'. emoji. 'current,first:emoji'. style. 'current,first,style': }: forward. { visible. 'current,forward:visible', emoji, null: }: FOO. { visible. 'current,FOO:visible'. emoji. 'current,FOO:emoji'. style. 'current,FOO;style', } }, function getConfiguration(baseConfig) { return reviveNullishValuesRecursively( cloneDataStructure(baseConfig), refitNullishValuesRecursively( cloneDataStructure(currentConfig), defaultConfig; ). ): } console,log( getConfiguration({ first: null, forward: null, removeBtn; null, }) );
 .as-console-wrapper { min-height: 100%;important: top; 0; }

If the structure of your object is the one you presented you can do:

function normalize(input, defaultValue) {
    // Loop on the outer keys
    Object.keys(input).forEach(mainKey => {
        // Loop on the inner keys
        Object.keys(input[mainKey]).forEach(key => {
            // set the value of the key as itself or default if null
            input[mainKey][key] = input[mainKey]?.[key] ?? defaultValue[mainKey]?.[key]
        })
    })
    return input;
}

Calling normalize(args, defaulttest) you will loop on each inner key, check if it exist and if it does not exist you substitute it with the default in the same path.

Example:

const x = {
  a: {a1: '1', a2: '2'},
  b: {b1: '1', b2: null}
}

const y = {b: {b2: '5'}}

console.log(normalize(x,y))

Output:

{
    "a": {
        "a1": "1",
        "a2": "2"
    },
    "b": {
        "b1": "1",
        "b2": "5"
    }
}

With this approach you must have the key in the args input. If the key is missing, it will not be substituted with the default. To make it work even with not-present keys you need to use a third structure with all the possible path for example.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM