3

How can I shallowly copy an JavaScript object and get rid of all non-primitive values (all references), while keeping all properties of the given object. Values of the properties might turn to null in this process.

Object.assign, lodash clone and the spread operator allow us to get a shallow copy of an object. However, despite the naming, the result object is not shallow [fact]. It has copied all references too, so the whole object tree is still accessible.

For a analytics solution, I need to get rid of anything deeper than one level.

How can I do it (libraries are also ok, ES6 is fine) without writing dozens of rules to deal with all the possible data types? Ideally, objects and array properties are not lost, but replaced with something, e.g. null or empty objects/arrays.

Example

const source = {
    nr: 1,
    str: 'ok',
    obj: {
        uhOh: 'kill me'
    },
    arr: ['well ok', { uhOh: 'uhOh' }],
}

// apply voodoo 

const expected = {
    nr: 1,
    str: 'ok',
    obj: {},
    arr: [],
}

// This would also be an valid result:
const expected = {
    nr: 1,
    str: 'ok',
    obj: null,
    arr: null,
}
dube
  • 4,898
  • 2
  • 23
  • 41
  • 5
    "It has copied all references too" - That's what shallow means! If it was deep it would duplicate the objects that are referenced and then reference the new objects. – Quentin Sep 04 '19 at 11:37
  • @Quentin I know what it means, and it's the cause of a problem for which I am looking for a clever solution – dube Sep 04 '19 at 11:41
  • I think the question is about how to shallow copy without copying either the references, nor duplicating the objects, but create default empty ones with new references? – William Chong Sep 04 '19 at 11:42
  • @WilliamChong Err, yes. It's "do not deep clone and get rid of any `value`, while keeping all the properties (of the root object) – dube Sep 04 '19 at 11:44
  • @Slai Thanks, but sadly that's not a duplicate. That one is about deep cloning, which I specifically do not want to do – dube Sep 04 '19 at 11:44
  • 1
    Is a function considered a deeper object, or should functions be copied as they are? – Teemu Sep 04 '19 at 11:46
  • @Teemu I would consider it a deeper object, like any values but primitives. – dube Sep 04 '19 at 11:49
  • 1
    Your second valid result is [Is there a proper way to clone certain primitive properties of an object in JavaScript?](https://stackoverflow.com/questions/44805944) and adding an `else` block for `if (!(source[key] instanceof Object)) {` with `target[key] = null` – t.niese Sep 04 '19 at 11:55
  • @t.niese I think you are right, thank you. – dube Sep 04 '19 at 11:58
  • Thanks to everyone for the quick responses. As for flagging it as "too broad", I disagree with this judgement. Maybe you didn't read the text all too carefully, looking at parts of it out of context (shallow object != shallow copy). – dube Sep 04 '19 at 12:06
  • @dube There are topics about cloning, there are various topics about to determine if the value is primitive. So one broadness due to that is, if your problem is related to the cloning and limiting it to level one, if it is to identify the primitives or if it is about how to choose the correct default for the none primitive (if you want your first valid result). And given that the one level cloning (like in my linked question) is just a few (and not dozens) lines, it can be expected that you already come up with some code and asking if e.g. a specific part about that code is reliable. – t.niese Sep 04 '19 at 12:24
  • @t.niese Isn't the second point an argument _against_ it being too broad? :-) Heck, the question even has examples of the expected conversion. I think all I forgot was a function and a Date, but [I thought] it outlines the rules quite clearly. I mainly asked because it felt like there must be an out-of-the-box solution, which I forgot to think of. Looping and checking the types myself feels so ... dirty. – dube Sep 04 '19 at 12:49
  • 1
    I didn't vote for closing. But I can understand it. Questions should - especially if parts of it already have been answered in other questions - be about specific parts of the problems. Otherwise, you have the risk that you get multiple permutations of the subproblems like `for .. in` vs `Object.keys`, ... or `typeof source[key] === 'object'` vs `typeof source[key] instanceof Object` each with its pros and cons. If one of the other questions now come up with a better solution for one of your subproblems it is not reflected in the answers to your question and that results in cluttering. – t.niese Sep 04 '19 at 13:08
  • 1
    But I also can understand your point as you look for an alternative to writing it yourself. But then it is always a good idea to link to an already existing similar question, show the simple solution you have and ask if there is a language feature that allows you to solve that in a more elegant way. But then the question is if that fits the code review stack exchange better. – t.niese Sep 04 '19 at 13:11

2 Answers2

2

You could loop through the keys of the object using for...in. If the value is an object, set the key to null in expected, else set the value in expected to the value from source

const source = {
  nr: 1,
  str: 'ok',
  obj: {
    uhOh: 'kill me'
  },
  arr: ['well ok', {
    uhOh: 'uhOh'
  }],
}

const expected = {};

for (const key in source) {
  if (typeof source[key] === 'object')
    expected[key] = null
  else
    expected[key] = source[key]
}

console.log(expected)
adiga
  • 34,372
  • 9
  • 61
  • 83
0

This is not an answer in it's own right, but an addendum to the excellent answer by @adiga, this time using typescript and a type parameter:

private primitiveClone<T>(source: T): T {
    const dto = Object.assign({}, source);
    for (const key in dto) {
        if (typeof dto[key] === 'object') {
            dto[key] = null;
        }
    }
    return dto;
}

usage

var simpleClone = primitiveClone(data);
Marcel
  • 15,039
  • 20
  • 92
  • 150