Deep clone without some fields

2020-06-12 04:15发布

问题:

Let's I have next javascript object. Now I want clone it but without some fields. For example I want cloned object without field "lastName" and "cars.age"
Input

{
   "firstName":"Fred",
   "lastName":"McDonald",
      "cars":[
           {
              "type":"mersedes",
              "age":5
           },
           {
              "model":"bmw",
              "age":10
           }
       ]
}  

Output (cloned)

{
   "firstName":"Fred",
   "cars":[
       {
          "model":"mersedes"
       },
       {
          "model":"bmw"
       }
   ]
}   

I can do something like

var human = myJson   
var clone = $.extend(true, {}, human)  
delete clone.lastName  
_.each(clone.cars, function(car))  
{  
   delete car.age  
}  

Do you know easier solution?

回答1:

If you don't mind adding to object prototypes, this is an easy solution. You may want to modify it some for your own use.

Object.prototype.deepOmit = function(blackList) {
  if (!_.isArray(blackList)) { 
    throw new Error("deepOmit(): argument must be an Array");
  }

  var copy = _.omit(this, blackList);
  _.each(blackList, function(arg) {
    if (_.contains(arg, '.')) {
      var key  = _.first(arg.split('.'));
      var last = arg.split('.').slice(1);
      copy[key] = copy[key].deepOmit(last);
    }
  });
  return copy;
};

Array.prototype.deepOmit = function(blackList) {
  if (!_.isArray(blackList)) { 
    throw new Error("deepOmit(): argument must be an Array");
  }

  return _.map(this, function(item) {
    return item.deepOmit(blackList);
  });
};

Then when you have an object like:

var personThatOwnsCars = {
   "firstName":"Fred",
   "lastName":"McDonald",
      "cars":[
           {
              "type":"mersedes",
              "age":5
           },
           {
              "model":"bmw",
              "age":10
           }
       ]
};

You can do magic like this.

personThatOwnsCars.deepOmit(["firstName", "cars.age"]);

Or even magic like this!

[person1, person2].deepOmit(["firstName", "cars.age"]);


回答2:

Here is a standalone function depending on lodash/underscore that i've written that does the same.

It calls a callback for each (value, indexOrKey) pair in the object or array and if true will omit that pair in the resulting object.

The callback is called after the value has been visited so you can omit a whole sub-tree of values that match your condition.

function deepOmit(sourceObj, callback, thisArg) {
    var destObj, i, shouldOmit, newValue;

    if (_.isUndefined(sourceObj)) {
        return undefined;
    }

    callback = thisArg ? _.bind(callback, thisArg) : callback;

    if (_.isPlainObject(sourceObj)) {
        destObj = {};
        _.forOwn(sourceObj, function(value, key) {
            newValue = deepOmit(value, callback);
            shouldOmit = callback(newValue, key);
            if (!shouldOmit) {
                destObj[key] = newValue;
            }
        });
    } else if (_.isArray(sourceObj)) {
        destObj = [];
        for (i = 0; i <sourceObj.length; i++) {
            newValue = deepOmit(sourceObj[i], callback);
            shouldOmit = callback(newValue, i);
            if (!shouldOmit) {
                destObj.push(newValue);
            }
        }
    } else {
        return sourceObj;
    }

    return destObj;
}

Some samples

var sourceObj = {
    a1: [ undefined, {}, { o: undefined } ],
    a2: [ 1, undefined ],
    o: { s: 's' } 
};

deepOmit(sourceObj, function (value) {
    return value === undefined;
});
//=> { a1: [ {}, {} ], a2: [ 1 ], o: { s: 's' }}

//omit empty objects and arrays too
deepOmit(sourceObj, function (value) {
    return value === undefined ||
        (_.isPlainObject(value) && !_.keys(value).length) ||
        (_.isArray(value) && !value.length);
});
//=> { a2: [ 1 ], o: { s: 's' }}

//indexOrKey is the string key or the numeric index if the object is array
deepOmit([ 0, 1, 2, 3, 4 ], function (value, indexOrKey) {
    return indexOrKey % 2;
});
//=> [ 0, 2, 4 ]