1

I have an object called cache

const cache = {
  a: {
    b: { c: [toRemove1, toPreserve1, toRemove2, toPreserve2], d: "irrelevant" }
  }
}

This object is typed, so we would know exactly where to find the level for things to remove

and I want to filter out toRemove1 toRemove2 specifically on the level of c, so we don't need to care about other levels in the cache.

The filtered result is { a: { b: { c: [toPreserve1, toPreserve2] , d: 'irrelevant'} } }

The function I came up with is this

function filter(cache, thingsToRemove, mapper) {
  const filtered = mapper(cache).filter(
    (thing) => !thingsToRemove.includes(thing)
  );
}

Here the mapper is a function that traverses to the level where we want to perform the filtering. In this case it is (cache) => cache.a.b.c

Here is the problem, while my current solution does the job of filtering, I can't seem to preserve the original structure of the cache, that means the result of mapper(cache).filter((thing) => !thingsToRemove.includes(thing)) is [toPreserve1, toPreserve2]. But I need the returned object has the same structure as it was before.

Here is the live demo: https://codesandbox.io/s/still-feather-zuhcl?file=/src/index.js

I am not sure if it is possible with the current API.... if not, what should be changed?

Also I am well aware that we can pass another param to indicate where is the last property, in this case is c. But I really would like to not need to explictly pass that param to achieve this.

I also know that I can add a path to the API that takes an array of paths. e.g. ["a", "b", "c"] But what I had in mind is to find a way to use a function to traverse it so that user of the API can rely on the type checking and auto complete rather than manually copy and paste the fields to form a path array.

Joji
  • 4,703
  • 7
  • 41
  • 86
  • Do you need the result to be a new object, or the old object, mutated? – CertainPerformance Nov 04 '20 at 18:15
  • Deep clone the cache, modify the clone, return the entire clone. – Taplar Nov 04 '20 at 18:16
  • new object. will update the question – Joji Nov 04 '20 at 18:16
  • @Taplar I know we can do it that way it is just I want to achieve it using the current fucntion – Joji Nov 04 '20 at 18:17
  • You are operating upon a subset of data. If you want to return the entire structure, you cannot return just that data. You have to explicitly return the structure you want. Futhermore, "This doesn't work, but I want to keep doing it like this" doesn't sound logical, eh? – Taplar Nov 04 '20 at 18:18
  • @Taplar I know it is not possible by just turning that subset of data. I guess I didn't express myself clearly. I meant if we stick with the function signature here, how can we tweet the implementation to achieve this. – Joji Nov 04 '20 at 18:40

1 Answers1

1

As the comment says:

Deep clone the cache, modify the clone, return the entire clone

Deep clone using JSON.stringify/JSON.parse or whatever method you prefer. Then you'll need a way to assign to the deep property, perhaps have the function accept the final property as an additional argument:

function filter(cache, thingsToRemove, mapper, lastProp) {
  const clonedCache = deepClone(cache);
  const lastObj = mapper(clonedCache);
  lastObj[lastProp] = lastObj[lastProp].filter(
    (thing) => !thingsToRemove.includes(thing)
  );
  return clonedCache;
}

and change the call of (cache) => cache.a.b.c to (cache) => cache.a.b while passing 'c' as a 4th argument.

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • yea but that requires you to pass the `lastProp`... which would be `c` in this case. I kinda wanted to avoid that. is there any other way without explictly passing `c`? – Joji Nov 04 '20 at 18:36
  • Not if you want to keep the original data structure unmutated. You could mutate the array by using `splice` instead (it looks really ugly). You could also pass a string of property accessors, eg `a.b.c`, then pop off the last property inside the function to identify what needs to be reassigned. – CertainPerformance Nov 04 '20 at 19:02
  • got it ... the reason I chose to use a function to traverse the object is because that way I can rely on the typing checking and auto complete. I know I can come up with something with `["a", "b", "c"]` to traverse the object but that still requires the user of the API to manually add the path by copy & paste – Joji Nov 04 '20 at 19:19