Skip to content
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

Handling "undefined" #39

Open
snicol21 opened this issue Nov 12, 2023 · 3 comments
Open

Handling "undefined" #39

snicol21 opened this issue Nov 12, 2023 · 3 comments

Comments

@snicol21
Copy link

Maybe this is by design, but I was surprised to learn that this:

import { merge } from "merge-anything"

const defaultTheme = {}
const theme = undefined

merge(defaultTheme, theme)

Returns undefined. Is this expected, maybe it is, but I was kind of expecting it to return {}. Am I right?

I would hate to have to handle this scenario like this everytime:

merge(defaultTheme, theme || {})
@mesqueeb
Copy link
Owner

@snicol21 It's by design 😅
i'm able to change it with a major version if you want.
When using merge to change props inside an object, you would probably want to keep the current implementation I imagine though. But Ach, I guess it's always case-by-case 🤷🏻‍♂️

@snicol21
Copy link
Author

@snicol21 It's by design 😅

i'm able to change it with a major version if you want.

When using merge to change props inside an object, you would probably want to keep the current implementation I imagine though. But Ach, I guess it's always case-by-case 🤷🏻‍♂️

Thanks for responding @mesqueeb, this is the wrapper I ended up creating to accommodate this use case.


import { merge as mergeAnything } from "merge-anything"

// Replaces 'null' or 'undefined' values with an empty object in a tuple,
// ensuring that all elements are valid objects for merging.
type Defined<T> = {
  [K in keyof T]: Exclude<T[K], null | undefined>
}

// Recursively merges a tuple of objects or null/undefined values,
// filtering out the null/undefined to prevent type errors during merge.
type MergeDefined<T> = T extends [infer First, ...infer Rest]
  ? First extends object
    ? Rest extends (object | undefined | null)[]
      ? First & MergeDefined<Rest>
      : never
    : never
  : unknown

// A merge function that combines multiple objects, including those
// that may be 'null' or 'undefined', into a single object with proper types.
export function merge<T extends (object | undefined | null)[]>(...objects: T): MergeDefined<T> {
  // Create an array of objects, converting 'null' or 'undefined' values to empty objects.
  const safeObjects = objects.map((obj) => (obj == null ? {} : obj)) as Defined<T>
  // Merge the objects into one, casting the result to the recursive merge type.
  return mergeAnything({}, ...safeObjects) as MergeDefined<T>
}

@quantizor
Copy link

Related to this, if you try to apply a partial of the first type it causes problems because the resulting type gains | undefined for all the partial fields where the point of it is just to selectively override fields, e.g.

const baseTokens: ColorTokensWithModes = {
  uiBg: 'white',
  uiBgAlternate: 'lightgray',
}

const tokens: ColorTokensWithModes = merge<ColorTokensWithModes, DeepPartial<ColorTokensWithModes>[]>(baseTokens, {
  uiBg: colors.white,
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants