-
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
Option to not let undefined values override already defined values? #28
Comments
Hmm, the tricky one here is that because you've used the However, if you know that the result is definitely going to be You can do it either like this: const theme: Theme = merge(defaultTheme, themeConfig); Or like this if you get an error with the above, if the inferred return type fails to align to const theme = merge(defaultTheme, themeConfig) as Theme; These will still allow for sufficient type safety, especially because you're working with objects with assigned types above the merge. I hope that helps! 🙏 |
Makes perfect sense @voodoocreation! However casting it as In my example it doesn't have any undefined values, but this use case would usually be in a package where the config would likely come from someone using the package, making casting it unsafe. |
Ahh true. Hmmm... I suppose I could add an option like Another option could be to handle default values differently in your implementation, such as explicitly defining the options object that uses the provided values with fallbacks to the defaults defined there, though I know it's much more convenient to simply call I suppose the question is... would you be happy enough if I added the |
I don't think I have the TypeScript skills to infer the correct types for endless params - however I do believe it could be achieved without a separate merge function with discriminating unions based on My solution in the meantime has been a modified version of the merge - which does what I want, but is limited to two parameters. If you do decide to include the option, maybe the import type { DeepPartial } from "./generic.js";
type PickRequired<T> = {
[L in Exclude<keyof T, OptionalKeys<T>>]: PickRequired<T[L]>;
};
type OptionalKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? K : never;
}[keyof T];
type Defaults<T, K> = {
[L in Exclude<keyof K, OptionalKeys<T>>]: L extends keyof T
? T[L] extends object
? PickRequired<T[L]>
: T[L]
: K[L];
} & T;
interface GenericObject {
[key: string]: any;
}
export const isObject = (obj: any): obj is object => {
if (typeof obj === "object" && obj !== null) {
if (typeof Object.getPrototypeOf === "function") {
const prototype = Object.getPrototypeOf(obj);
return prototype === Object.prototype || prototype === null;
}
return Object.prototype.toString.call(obj) === "[object Object]";
}
return false;
};
export const mergeDefaults = <
T extends GenericObject,
K extends DeepPartial<T> = DeepPartial<T>
>(
defaults: T,
overrides: K,
options: {
mergeArrays: boolean;
uniqueArrayItems: boolean;
} = {
mergeArrays: true,
uniqueArrayItems: true,
}
): Defaults<T, K> => {
/** @Todo Deep clone */
const copy: GenericObject = { ...defaults };
Object.keys(overrides).forEach(key => {
if (["__proto__", "constructor", "prototype"].includes(key)) {
return;
}
if (Array.isArray(copy[key]) && Array.isArray(overrides[key])) {
if (options.mergeArrays) {
copy[key] = mergeArrays(
copy[key],
overrides[key],
options.uniqueArrayItems
);
} else {
copy[key] = overrides[key];
}
} else if (isObject(copy[key]) && isObject(overrides[key])) {
copy[key] = mergeDefaults(copy[key], overrides[key], options);
} else if (overrides[key] !== undefined) {
copy[key] = overrides[key];
}
});
return copy as Defaults<T, K>;
};
export const mergeArrays = <T, K>(
initial: T[],
additions: K[],
unique: boolean = true
) => {
const mergedArray = [...initial, ...additions];
if (unique) {
return new Set(mergedArray);
}
return mergedArray;
}; Personally I'd be happy without the inferred types, but I understand the inferred type is a strength of this package, and as you see, I do have a solution working for me (for two objects) even if you decide not to add this option |
Thanks for the snippet - will see what I can come up with. At the very least, I'll add the new option and runtime behaviour - it's just the type inferring that may be tricky. I should have some time either later today or tomorrow to investigate 🙏 |
Hey! Have just released 6.2.0 which has the runtime behaviour via the |
Hi!☺️ I noticed that if you have default values followed by partial versions of the initial object, it returns that everything is possibly undefined.
This might be true as you could specifically set a value to be undefined and that would override it, but if there was an option to avoid this, the type could return the full type rather than a partial version.
This option would make it work more ideally for cases like the example below where you'd want to merge default options with some configurations.
The text was updated successfully, but these errors were encountered: