onkami
onkami

Reputation: 9411

Elegant way to code partially copy objects in Javascript?

I have a javascript object that represents form fields. about 70% of these fields have to be copied in some objects for sending to server, other fields are for UI usage. Presently I clone objects by manually creating assignments for each field, that result in rather not nice structures, as shown below. Note that I would keep field names identical for parts being copied.

var contData = {
                ContainerType: data.ContainerType,
                ProjectIds: data.ProjectIds,
                PrivateToProjects: data.PrivateToProjects,
                DimensionType:data.DimensionType,
                MetricsX: data.MetricsX,
                MetricsY: data.MetricsY,
                Parent:data.Parent,
                ContainerName:data.Prefix
            };

What would be the best way to code cloning part of object, just specifying list of fields to clone/not to clone, such as some useful helper function?

I also use angular and jquery.

Upvotes: 3

Views: 3216

Answers (4)

Ricardo
Ricardo

Reputation: 821

Dan's destructuring answer is good, but it still requires an assignment, making it less than ideal (however still possible) for lambda, or arrow-style, concatenation. It remains a great way to filter out object fields when passing parameters to a functions, for instance

Here's an alternative that you can embed in an expression and that doesn't use assignment side-effects:

wanted = ['ContainerType', 'ProjectIds', 'PrivateToProjects',
         'DimensionType', 'MetricsX', 'MetricsY', 'Parent', 'Prefix']

contData = Object.fromEntries(Object.entries(data).filter(x => wanted.includes(x[0]))))

The only state this requires is the wanted fields. If you want to preserve most fields in data, you can always use notWanted instead and negate the filter condition.

Upvotes: 0

Dan
Dan

Reputation: 1078

After ES6, you could

let { ContainerType, ProjectIds } = data  // the fields you want
let partiallyCopy = { ContainerType, ProjectIds }
console.log(partiallyCopy)  // { ContainerType: "...", ProjectIds: "..." }

And if you need most fields, you could

let { ContainerType, ProjectIds, ...rest } = data  // the fields you don't want
let partiallyCopy = rest
console.log(partiallyCopy)  // the object excludes ContainerType and ProjectIds

Upvotes: 10

Brigand
Brigand

Reputation: 86240

One method is to define properties on objects. IE9 is the first IE to support this.

var obj = {};
Object.defineProperty(obj, "no1", {enumerable: false, value: "", writable: true, configurable: true});
Object.defineProperty(obj, "no2", {enumerable: false, value: "", writable: true, configurable: true});

obj.yes1 = "foo";
obj.yes2 = "bar";
obj.no1 = "baz";
obj.no2 = "quux";

jsfiddle

99.9% of clone functions will loop over the keys, and only enumerable keys will show up, so they only copy enumerable keys. This is the same reason why, e.g. toString doesn't show up when looping over an object's keys.

This can be abstracted to allow defining data and temporary values.

function makeType(description, data) {
    if (arguments.length === 1) {
        return function (data) {
            return makeType.call(this, description, data);
        };
    }

    var obj = {};
    data = data || {};
    for (var key in description) {
        if (description[key] === true) {
            obj[key] = data[key]
        } else {
            Object.defineProperty(obj, key, {
                enumerable: false,
                value: data[key],
                writable: true,
                configurable: true
            });
        }
    }
    return obj;
}

var makeYesNo = makeType({
    yes1: true,
    yes2: true,
    no1: false,
    no2: false
});

var obj = makeYesNo({
    yes1: "foo",
    yes2: "bar",
    no1: "baz",
    no2: "quux"
})

fiddle

Upvotes: -1

harun
harun

Reputation: 1919

You could create a custom function to clone your object partially with a filter function.

It could be something like this as the very simple version.

function filteredClone(sourceObj, filterFunction){
  var destObj = {};
  for(var i in sourceObj){
    if(filterFunction(sourceObj[i])){
      destObj[i] = sourceObj[i];
    }
  }
  return destObj;
}

And you can call it like the following assuming that you don't want "name" and "surname" fields to be copied.

var dest = filteredClone(source, function(v){
   return ["name","surname"].indexOf(v) !== -1;
});

There are a couple of more sophisticated samples in the answers to the following question.

Deep clone without some fields

Upvotes: 0

Related Questions