-
-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Key based array merging #23
Comments
This can already be done with Here's an implementation of merging arrays based on a element's key: ////////////////////
// utils.ts
import { deepmerge, deepmergeCustom, type DeepMergeOptions } from "deepmerge-ts";
export function hasProp<T, K extends PropertyKey>(
value: T,
prop: K
): value is T & Record<K, unknown> {
return typeof value === "object" && value !== null && prop in value;
}
export function myArrayDeepmerge<K extends PropertyKey>(id: K) {
const mergeSettings: DeepMergeOptions = {
mergeArrays: (values, utils) => {
const valuesById = values.reduce<Map<unknown, unknown[]>>(
(carry, current) => {
const currentElementsById = new Map<unknown, unknown>();
current.forEach((element) => {
if (!hasProp(element, id)) {
throw Error("Invalid element type"); // You can change this to simply ignore the value or keep it as is, rather than throwing.
}
if (currentElementsById.has(element[id])) {
throw Error("multiple elements with the same id"); // You can change this to simply ignore duplicates. In which case `currentElementsById` is not longer needed. If you can guarantee this else where then that also justifies removing this check.
}
currentElementsById.set(element[id], element);
const currentList = carry.get(element[id]) ?? [];
carry.set(element[id], [...currentList, element]);
});
return carry;
},
new Map<unknown, unknown[]>()
);
return [...valuesById.values()].reduce(
(carry, values) => [...carry, deepmerge(...values)],
[]
);
}
};
return deepmergeCustom(mergeSettings);
}
////////////////////
// some-other-file.ts
import { myArrayDeepmerge} from "./path/to/utils.ts";
const x = [
{ id: 1, value: "a" },
{ id: 2, value: "b" }
];
const y = [
{ id: 2, value: "b updated" },
{ id: 1, value: "a updated" }
];
const merged = myArrayDeepmerge("id")(x, y);
console.log(merged);
// [{id: 1, value: "a updated"}, {id: 2, value: "b updated"}] As this merge function is designed to merge arrays of the same type; you shouldn't need to provide any fancy HKT definitions. The default ones should work just fine. Using key paths is also possible but you would need to provide me with more details on the how you want it to work for me to implement it. With regard to adding this sort of merge function directly to the library. I am open to it but I'll need more feedback from everyone. We'd also need to nail down what should happen in the error cases. |
Thank you for your detailed answer! Basically what I mean is that the key paths provide a way to reach a key, starting from the root (the objects we pass to An example object and key path: const x = {outerArr: [{arr: [{id: 1}]}, {arr: [{id: 2}]}]};
const keyPaths = ["outerArr.arr.id"] So const y = {outerArr: [{arr: [{id: 3, newProp: "newProp"}]}]}; For the outer array we don't have any key path, so default merging should apply. But it wouldn't be useful it the default array merging logic here would be concatenating the arrays. It would need to be a combine approach that is based on same-index-logic meaning there is an attempt to merge {outerArr: [{arr: [{id: 1}, {id: 3, newProp: "newProp"}]}, {arr: [{id: 2}]}] } Instead if the ids would match (imagine {outerArr: [{arr: [{id: 1, newProp: "newProp"}]}, {arr: [{id: 2}]}] } It could also be that the key path isn't applicable to one (or even both) object(s) passed to deepmerge. Like with the following const y = {outerArr: []}; In that case normal merging applies (which would return an object that is identical to Also I wouldn't make the decision to apply combine-based-logic instead of concat-based-logic on key paths, instead I would maybe offer it as option for the whole merging process. |
I'm going to close this issue for now and track the progress in #33. |
@NightProgramming I've just released a beta for version 3 that has the meta data feature in it. Here's a test I added that does key-based array merging: deepmerge-ts/tests/deepmerge-custom.test.ts Lines 409 to 530 in 42e0207
|
@RebeccaStevens Would it be possible to make the custom merge code a bit shorter by not having to have to code the default merging? So that writing only Of course we would then also need a way to be able to tell the library explicitly that we want to use Or the custom merger could receive a callback (used like Maybe we could even have the option to skip merging (via |
@RebeccaStevens Also, would it be possible to get some more meta data? Assuming a property value should be set, based on values from other properties. It would be nice to not only get the property values and the property name but also be able to get the property owners. Example: const x = {a: 1, isBadObject: true};
const y = {a: 2, isBadObject: false};
...
mergeOthers: (values, utils, meta) => {
const { key } = meta;
if (key === "isBadObject") {
return false; // The overall merge result object will be a good object :)
}
if (key === "a") {
const owners = meta.getOwners();
let resultSum = 0;
for (let i = 0; i < values.length; i++) {
if (!owners[i].isBadObject) {
resultSum += values[i];
}
else {
// Skip bad object.
}
}
return resultSum ;
}
return; // Trigger default merging (see my above post)
} I used a function call to get the owners, because otherwise it might not be good for performance if Via |
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
I think providing the possibility to merge based on key instead of order would be nice:
Maybe also provide the option to provide paths:
The text was updated successfully, but these errors were encountered: