简体   繁体   English

比较 2 个嵌套数据结构,目标 + 源,与源对应物相比,缺少目标值的适当合并策略是什么?

[英]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.如果args对象的值为空值,则从default object访问非空值并将其分配给最终结构。

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. 1/2 ... OP 实际上并没有真正进行比较。 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.对于一组特定的属性,OP 在目标 object 处查找每个属性,并且只有在它具有空值的情况下,才会从源对象的对应项分配给缺失的属性。 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. 2/2 ... 实现一个通用的 function 合并两个对象,只有在目标结构尚未提供非无效值的情况下才能写入/分配源属性。 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 .这个 function 然后必须被调用两次,一次用于argsdefaulttest ,第二次用于返回完全空的数据结构和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.一个称为refit ,由于强制将源对象中的每个非无效属性分配给目标对象的非无效对应项,因此它遵循推送/修补议程。

  • 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.第二个,称为revive ,它类似于拉式方法,因为它只是将无效的目标对象属性重新分配给它们的非无效源对象属性。

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.至于以创建某种配置对象为目标的 OP 示例,可以完全修补/改装临时或当前args -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:如果您的 object 的结构是您提供的结构,您可以执行以下操作:

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.调用normalize(args, defaulttest)您将循环每个内部键,检查它是否存在,如果不存在,您将其替换为同一路径中的默认值。

Example:例子:

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

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

console.log(normalize(x,y))

Output: Output:

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

With this approach you must have the key in the args input.使用这种方法,您必须在 args 输入中拥有密钥。 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.为了使其即使在不存在的键下也能工作,您需要使用第三个结构,例如所有可能的路径。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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