Given the following object:
{
__proxy: {
state: {
count: 0
items: {
__proxy: {
state: {
amount: 0
}
}
}
}
}
}
I'd like to convert it to:
{
count: 0,
items: {
amount: 0
}
}
So, as you can see, I'm doing a few things:
__proxy and bringing its content upstate and bringing its content upI've tried something like the snippet below:
const removeKeys = (obj, keys) => obj !== Object(obj)
? obj
: Array.isArray(obj)
? obj.map((item) => removeKeys(item, keys))
: Object.fromEntries(Object.entries(obj).filter(([k]) => !keys.includes(k)));
removeKeys(myObj, ['__proxy', 'state'])
However, it completely removes __proxy and/or state - and I want to preserve their content.
That said, do you know any existing solution for that? An NPM library, perhaps? Or a lodash function?
Note: Since I'm using TypeScript, a typed solution would be preferred, but raw js works well either.
Thanks!
You could destructure the unwanted properties and move the content a level up.
const
convert = ({ __proxy, state, ...object }) => __proxy || state
? convert({ ...(__proxy || {}), ...(state || {}), ...object })
: Object.fromEntries(Object
.entries(object)
.map(([k, v]) => [k, v && typeof v === 'object' ? convert(v) : v])
)
data = { __proxy: { state: { count: 0, items: { __proxy: { state: { amount: 0 } } } } } },
result = convert(data);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Usually for these problems I start on the types first. Sometimes making the type gives me ideas on how to implement it in code, so here we go!
Let's first define a type that does this (I call it "obliteration" for whatever reason) for one key only:
type Obliterate<O, K> = O extends object ? { [P in Exclude<keyof O, K>]: Obliterate<O[P], K> } & (K extends keyof O ? Obliterate<O[K], K> : {}) : O;
If O is not an object, there is nothing to remove from it.
However if it is, the following steps are taken:
K from O and then recursively traverse down the object and "obliterate" all the values.K is a key of O and if it is we intersect it with the result from the first step. If it isn't we intersect it with {} which does nothing.Then a type that takes a list of keys and iterates over it and uses Obliterate on the target:
type ObliterateEach<O, K> = K extends readonly [infer F, ...infer M] ? ObliterateEach<Obliterate<O, F>, M> : O;
If K is not empty, we operate on the object, otherwise we are done and return the object.
With these types I have poorly written the following code that both mirror how these types work:
const obliterate = <O, K extends PropertyKey>(o: O, k: K): Obliterate<O, K> => (
(o && typeof o === "object")
? Object.assign(Object.fromEntries(Object.entries(o).filter(([p]) => p !== k).map(([p, v]) => [p, obliterate(v, k)])), k in o ? obliterate((o as any)[k], k) : {})
: o
) as Obliterate<O, K>;
const obliterateEach = <O, K extends ReadonlyArray<PropertyKey>>(o: O, k: K): ObliterateEach<O, K> => (
k.reduce<{}>((r, k) => obliterate(r, k), o)
) as ObliterateEach<O, K>;
You can read these functions the same as the types above. They are the exact same steps, just in code and not types (and a few casts to suppress type errors).
This is most certainly not the best strongly typed solution, but it's a start.
A playground demonstrating this solution.