Giter Club home page Giter Club logo

ts-deepmerge's Introduction

npm

TypeScript Deep Merge

A deep merge function that automatically infers the return type based on your input, without mutating the source objects.

Objects and arrays will be merged, but values such as numbers and strings will be overwritten.

All merging/overwriting occurs in the order of the arguments you provide the function with.

Both ESM and CommonJS are supported by this package.

Usage

import { merge } from "ts-deepmerge";

const obj1 = {
  a: {
    a: 1
  }
};

const obj2 = {
  b: {
    a: 2,
    b: 2
  }
};

const obj3 = {
  a: {
    b: 3
  },
  b: {
    b: 3,
    c: 3
  },
  c: 3
};

const result = merge(obj1, obj2, obj3);

The value of the above result is:

{
  "a": {
    "a": 1,
    "b": 3
  },
  "b": {
    "a": 2,
    "b": 3,
    "c": 3
  },
  "c": 3
}

With options

If you would like to provide options to change the merge behaviour, you can use the .withOptions method:

import { merge } from "ts-deepmerge";

const obj1 = {
  array: ["A"],
};

const obj2 = {
  array: ["B"],
}

const result = merge.withOptions(
  { mergeArrays: false },
  obj1,
  obj2
);

The value of the above result is:

{
  "array": ["B"]
}

All options have JSDoc descriptions in its source.

When working with generic declared types/interfaces

There's currently a limitation with the inferred return type that ts-deepmerge offers, where it's unable to take the order of the objects/properties into consideration due to the nature of accepting an infinite number of objects to merge as args and what TypeScript currently offers to infer the types. The primary use case for the inferred return type is for basic object primitives, to offer something more useful as the return type, which does work for a lot of cases.

If you're working with generic declared types though, this can cause the inferred return type to not align with what you may expect, as it currently detects every possible value and combines them as a union type. When working with declared types, and you know what the final type will align to, simply use the as keyword as shown in the example below:

interface IObj {
  a: string;
  b: string;
}

const obj1: IObj = { a: "1", b: "2", };
const obj2: Partial<IObj> = { a: "1" };

const result = merge(obj1, obj2) as IObj;

More context can be found in this issue.

ts-deepmerge's People

Contributors

dependabot[bot] avatar faithfinder avatar trainiac avatar voodoocreation avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

ts-deepmerge's Issues

Source Map error when using Webpack 5

When ts-deepmerge is used in a Webpack 5 project and the project is built, Webpack generates the following warning:

Failed to parse source map from '«path»\node_modules\ts-deepmerge\src\index.ts' file: Error: ENOENT: no such file or directory, open '«path»\node_modules\ts-deepmerge\src\index.ts'

The fix is easy: change "sourceMap": true, to "sourceMap": false, in tsconfig.json.

Option to not let undefined values override already defined values?

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.

interface Theme {
  button: {
    background: string;
    color: string;
  }
}

const defaultTheme: Theme = {
  button: {
    background: '#a1a1a1',
    color: '#dedede'
  }
}

const themeConfig: DeepPartial<Theme> = {
  button: {
    color: '#fff'
  }
}

/**
 * Everything is possibly undefined.
 * While I'd wish for it to ignore undefined values from themeConfig,
 * and return the full type for Theme, not DeepPartial<Theme> 
 */
const theme = merge(defaultTheme, themeConfig)

Deep merge of classes with private properties

Hi, I'm encountering an issue when merging objects that have ES6-class-valued properties that have private properties on them. An example:

class A {
  constructor(private readonly b: number) {}
}

interface C {
  a: A;
}

const d: C = merge({ a: new A(1) }, { a: new A(2) });

This example does not compile with typescript 5.1.6, @tsconfig/node20/tsconfig.json, and ts-deepmerge > 4.0.0 (it works <= 4.0.0).
I can see both sides of this being a bug or not - curious what you think. If you don't consider this a bug, do you have any ideas about a workaround?

deepMerge is not a function

The compiled js can't use

{
  default: [Function: merge] {
    options: { mergeArrays: true },
    withOptions: [Function (anonymous)]
  }
}

TypeError: deepMerge is not a function

Duplicates in arrays getting removed

Is there a way to prevent removing of duplicates in a merged array?

Example

import merge from "ts-deepmerge";

var color = {
    color: [
        "#FC4",
        "#FC4",
        "#FC4",
        "#F00",
        "#F00",
        "#44F",
        "#44F",
        "#44F",
        "#000",
        "#000",
        "#000",
        "#44F",
        "#44F",
        "#44F",
        "#44F"
    ]
}

var other = {
    color: []
}

console.log(merge(color, other))

I expect the above to output

{
  color: [
    '#FC4', '#FC4', '#FC4',
    '#F00', '#F00', '#44F',
    '#44F', '#44F', '#000',
    '#000', '#000', '#44F',
    '#44F', '#44F', '#44F'
  ]
}

but instead it outputs

{ color: [ '#FC4', '#F00', '#44F', '#000' ] }

deepmerge 2.0.6 requires node 16?

#0 90.24 error [email protected]: The engine "node" is incompatible with this module. Expected version ">=16". Got "14.20.0"

We were running 2.0.1 on Google cloud with node 14 up until last rebuild which somehow replaced 2.0.1 with 2.0.6 through firebase-functions-test.

Possibility of *MergeOptions* of some sort

I forked the project today and added options as I needed them for a project of mine.

interface MergeOptions {
  mergeArrays?: boolean;
  merger?: Merger;
}

type Merger = (a: any, b: any, defaultMerger: (a: any, b: any) => any, options: { mergeArrays: boolean }) => any;

My changes allow to configure arrays being merged or replaced and allows to provide a default merger to provide additional custom logic. The default merger can be called from within the merger to allow for easy extensibility while keeping basic functionality working.

I made too many code changes to the fork already, but the changes could be ported over to this project if desired - You can see this issue as an Idea/Feature Request of some sort.

https://github.com/SpraxDev/ts-deepmerge/blob/c7c60b1c2a113a62e7b23e021902214a4ecf7469/src/index.ts#L17

Tell me what you think ^^

`isObject` false negative in some cases

Hi. Great library!

I seem to have come across a strange issue with the object comparison. For example, the prototype comparison doesn't work when using Object.create, I came across this issue with some default exports from a CJS module, which I haven't managed to repro fully, but this seems to be the crux of the problem:

Object.getPrototypeOf(Object.create({bool: true})) === Object.prototype // false

Was there a specific reason for including prototype comparison in isObject. Could it be simplified to just typeof comparison, for example the isObject lodash function is implemented as so:

function isObject(value) {
  const type = typeof value
  return value != null && (type === 'object' || type === 'function')
}

TS misunderstands types

Hi

export type Config = {
    presets?: {
        /** @default false */
        flexGrid?: boolean
        /** @default false */
        fontWeightRegular?: boolean
        /** @default true */
        moreDefaultValues?: boolean
        /** @default false */
        screenToDynamicScreen?: boolean
    }
    utilities?: {
        /** @default true */
        bgGrid?: boolean
        /** @default true */
        bgRadial?: boolean
        /** @default true */
        dir?: boolean
        /** @default true */
        drag?: boolean
        /** @default true */
        flip?: boolean
        /** @default true */
        hideShow?: boolean
        /** @default true */
        inputResets?: boolean
        /** @default true */
        insetCenter?: boolean
        /** @default true */
        overflowUnset?: boolean
        /** @default true */
        tapHighlight?: boolean
    }
    variants?: {
        /** @default true */
        notVariants?: boolean
    }
}
const defaultConfig = {
    presets: {
        flexGrid: false,
        fontWeightRegular: false,
        moreDefaultValues: true,
        screenToDynamicScreen: false,
    },
    utilities: {
        bgGrid: true,
        bgRadial: true,
        dir: true,
        drag: true,
        flip: true,
        hideShow: true,
        inputResets: true,
        insetCenter: true,
        overflowUnset: true,
        tapHighlight: true,
    },
    variants: {
        notVariants: true,
    },
} satisfies Config
export default (userConfig?: Config) => {
    const config = merge(defaultConfig, userConfig || {})
}

The issue is, TS shows me this error, for example, fontWeightRegular in config.presets?.fontWeightRegular can be undefined.

It's expected that fontWeightRegular will always have a value. But TS doesn't know that.

I want to merge the default options with the same options (with custom values) added by the user.

IObject: Length Keyword

Why is the key length a reserved property on IObject? I cannot find a reason why -- hoping for either clarify, or removal :) I am running into issues with my object that has a property called length

Incompatible objects are allowed to be merged

Forgive me if this isn't an issue but I would expect TS to prevent me from merging two non-overlapping objects.

If I have a variable of type Blah

interface Blah {
  key: string;
  value: string;
}

const source: Blah = {
  key: 'abc',
  value: 'def',
};

and I try to merge it with something which is not type Blah, I'd expect to see an error.

merge(source, { key: 'boop', ruh: 'roh' });

This is allowed, however. If I try to tell merge what types to expect, I get a different error:

merge<Blah>(source, { key: 'boop', ruh: 'roh' });
// TS2344: Type 'Blah' does not satisfy the constraint 'IObject[]'.
// Type 'Blah' is missing the following properties from type 'IObject[]': length, pop, push, concat, and 31 more

Using the normal deepmerge package gives me what I'd expect though.

merge<Blah>(source, { key: 'boop', ruh: 'roh' });
// TS2345: Argument of type '{ key: string; ruh: string; }' is not assignable to parameter of type 'Partial<Blah>'.
// Object literal may only specify known properties, and 'ruh' does not exist in type 'Partial<Blah>'.

Typescript 5

Currently the library is pinned to "typescript": "^4.9.4". Can we upgrade the dep to allow for 5?

Merge to eliminate optionals

Consider this snippet:

interface Foo {
  x: number;
  y: number;
}

const a: Foo = { x: 1, y: 2 };
const b: Partial<Foo> = { x: 3 };

const merged = merge(a, b);

In this snippet, I would expect the values to get merged. They are. So far so good. But I would expect the same for the types. Merging a required and optional member of the same name, should result in that type becoming a required one. That is, among others, what I think merging types should be about.

It should be more than just simply "orring" them together.

License

Hey, thanks you work on this package.

Do you mind adding a license?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.