-
-
Notifications
You must be signed in to change notification settings - Fork 146
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
Assigning/merging objects recursively while leaving arrays intact #136
Comments
I think that we have something pretty close atm, I can produce you a
prototype by tomorrow that will match your requirements. Any reason why
you're not willing to merge arrays? It's not a problem, just a question 😉
(we have full array support now, even with nested objects).
The versions of Merge, Patch, PatchAll, MergeAll, Assign already handle
built-in objects like Date not to alter them.
I'm not sure if there was a way to distinguish object type literals from
classes. I think this could be possible with type inference on the
constructor. I'll definitely look into it tomorrow!
…On Mon, Aug 3, 2020, 00:10 Kristóf Poduszló ***@***.***> wrote:
🤔 Question Describe your question
I'm currently working on a deepAssign function which calls Object.assign
recursively on object literals (but not arrays or instantiated classes).
The header looks as follows:
export default function deepAssign<
T extends { [key: string]: any },
U extends ReadonlyArray<{ [key: string]: any }>
>(target: T, ...sources: U): O.Assign<T, U, "deep">;
I would like the following object:
deepAssign(
{ arr: [1, 2, 3] as const },
{ arr: [4] as const, b: null },
{ b: 'test', c: { d: new Date() } },
{ c: { d: { e: 'Not merged with date properties' } },
);
To match the following type:
type T = {
arr: [4], // Not [4, 2, 3]
b: string,
{ c: { d: { e: 'Not merged with date properties' } },
};
How should this be done?
Search tags, topics
#deepAssign #deepMerge #object
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#136>, or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AEMMUDNAIDT3IYJZQR63RNTR6XP5NANCNFSM4PSYLAPA>
.
|
Thank you, I’m eagerly waiting for how this will turn out. As for the arrays, I’m going to treat them like tuples, which are immutable by nature. My implementation of |
Hey @kripod, there's just a few problems that I see right now. Let's say that the I have a tool But there is this, unfortunately, there is no way to distinguish between an object created via a class or an object literal. This issue microsoft/TypeScript#3841 is our best hope atm. Here's a summary of what I've concluded after trying to do this with class X {
x() {}
}
type IsClass<A extends any> =
A extends new (...args: any[]) => any
? 1
: 0
const x = new X() // create x
type t0 = IsClass<X> // works: nope. X is the type yielded by calling new on X
type t1 = IsClass<typeof X> // works: yes. typeof X is the type of the class itself
const t1: X = new X() // proves what we've said above
const t2: typeof X = X // proves what we've said above
declare function isClass<O>(o: O): IsClass<typeof O>
// if `O` is of type `X` it should yield `1`
const t = isClass(x) // does not work :( |
Thank you for these ideas! I'm not quite sure whether the 'with |
@kripod do you still want me to build this for you, regardless of the problems with the class instances? Or maybe I just misunderstood when you said classes, did you mean built-in objects must not be merged, but merge object literals and regular class instances? |
I would highly appreciate if you could come up with a solution to this, even an imperfect one would suffice 😊 For the initial version of the package I'm working on, merging types of regular type instances is a quirk I could live with. (I saw something in the issue you've linked which might be interesting.) Also, I'll make sure to add you as a contributor once I'll have the chance to release the small utility library. |
Mhh, I just tested it, and it does not seem to work. This is very frustrating, you're not the first one to ask. Ok, great! So I'll work on this very soon for you. Will ping you when it's done 👍 |
Hey Kristof, I think that it's done. Just let me know if it works for you! You must just install the import { Cast } from "Any/Cast"
import { Extends } from "Any/Extends"
import { Key } from "Any/Key"
import { Iteration } from "Iteration/Iteration"
import { IterationOf } from "Iteration/IterationOf"
import { Next } from "Iteration/Next"
import { Pos } from "Iteration/Pos"
import { Length } from "List/Length"
import { List } from "List/List"
import { BuiltInObject } from "Misc/BuiltInObject"
import { AtBasic } from "Object/At"
import { _OptionalKeys } from "Object/OptionalKeys"
import { Anyfy, Depth } from "Object/_Internal"
type MergeProp<OK, O1K, K extends Key, OOK extends Key> =
[K] extends [never]
? [OK] extends [never]
? O1K
: OK
: K extends OOK // if prop of `O` is optional
? NonNullable<OK> | O1K // merge it with prop of `O1`
: [OK] extends [never] // if it does not exist
? O1K // complete with prop of `O1`
: OK
type __MergeFlat<O extends object, O1 extends object, OOK extends Key = _OptionalKeys<O>> = {
[K in keyof (Anyfy<O> & O1)]: MergeProp<AtBasic<O, K>, AtBasic<O1, K>, K, OOK>
} & {}
type _MergeFlat<O extends object, O1 extends object> =
__MergeFlat<O, O1>
type MergeFlat<O extends object, O1 extends object> =
O extends unknown
? O1 extends unknown
? _MergeFlat<O, O1>
: never
: never
type __MergeDeep<O extends object, O1 extends object, OOK extends Key = _OptionalKeys<O>> = {
[K in keyof (Anyfy<O> & O1)]: _MergeDeep<AtBasic<O, K>, AtBasic<O1, K>, K, OOK>
} & {}
type ChooseMergeDeep<OK, O1K, K extends Key, OOK extends Key> =
OK extends BuiltInObject | List
? MergeProp<OK, O1K, K, OOK>
: O1K extends BuiltInObject | List
? MergeProp<OK, O1K, K, OOK>
: OK extends object
? O1K extends object
? __MergeDeep<OK, O1K>
: MergeProp<OK, O1K, K, OOK>
: MergeProp<OK, O1K, K, OOK>
type _MergeDeep<O, O1, K extends Key, OOK extends Key> =
[O] extends [never]
? MergeProp<O, O1, K, OOK>
: [O1] extends [never]
? MergeProp<O, O1, K, OOK>
: ChooseMergeDeep<O, O1, K, OOK>
type MergeDeep<O, O1> =
O extends unknown
? O1 extends unknown
? _MergeDeep<O, O1, never, never>
: never
: never
type Merge<O extends object, O1 extends object, depth extends Depth = 'flat'> = {
'flat': MergeFlat<O, O1>
'deep': MergeDeep<O, O1>
}[depth]
type __Assign<O extends object, Os extends List<object>, depth extends Depth, I extends Iteration = IterationOf<'0'>> = {
0: __Assign<Merge<Os[Pos<I>], O, depth>, Os, depth, Next<I>>
1: O
}[Extends<Pos<I>, Length<Os>>]
type _Assign<O extends object, Os extends List<object>, depth extends Depth> =
__Assign<O, Os, depth> extends infer X
? Cast<X, object>
: never
export type Assign<O extends object, Os extends List<object>, depth extends Depth = 'deep'> =
O extends unknown
? Os extends unknown
? _Assign<O, Os, depth>
: never
: never
/**
* Mini spec
*/
type t0 = Assign<{}, [{a: {b: {c: 1}}}, {a: {b: {c: 2, d: 3}}}]>
type t1 = Assign<{}, [{a: {b: {c: 1}}}, {a: {b: {c: undefined, d: 3}}}]>
type t2 = Assign<{}, [{a: {b: {c: 1}}}, {a: {b: {c?: 2, d: 3}}}]>
type t3 = Assign<{}, [{a: {b: {c?: 1}}}, {a: {b: {c?: 2, d: 3}}}]>
type t4 = Assign<{}, [{a: {b: {c: 1}}}, {a: {b: {c?: 2, d: 3}}}]>
type t5 = Assign<{}, [{a: [1, 2, 3]}, {a: [1]}]>
type t6 = Assign<{}, [[], {a: 1}]> |
@kripod, is this what you wanted? |
Yes, that seems to do it, thank you! It would be neat is there was a more convenient built-in method specifically for this. For instance, adding a new |
Are you sure it's that complex? 🤣 No worries, I understand that you don't want to maintain such a thing. It's a good idea to keep generic, thanks. Instead, I think I could add a parameter So I'm marking this as a feature request! |
It's done! So you will use ramda's merging style ( import {L, O, M} from 'ts-toolbelt'
type DeepAssign<O extends object, Os extends L.List<object>> =
O.Assign<O, Os, 'deep', 1, M.BuiltInObject | L.List>
/**
* Mini spec
*/
type t0 = DeepAssign<{}, [{a: {b: {c: 1}}}, {a: {b: {c: 2, d: 3}}}]>
type t1 = DeepAssign<{}, [{a: {b: {c: 1}}}, {a: {b: {c: undefined, d: 3}}}]>
type t2 = DeepAssign<{}, [{a: {b: {c: 1}}}, {a: {b: {c?: 2, d: 3}}}]>
type t3 = DeepAssign<{}, [{a: {b: {c?: 1}}}, {a: {b: {c?: 2, d: 3}}}]>
type t4 = DeepAssign<{}, [{a: {b: {c: 1}}}, {a: {b: {c?: 2, d: 3}}}]>
type t5 = DeepAssign<{}, [{a: [1, 2, 3]}, {a: [1]}]>
type t6 = DeepAssign<{}, [[], {a: 1}]> |
Marvelous, thank you so much for creating this in such a short time! 🙏 |
Unfortunately, it turns out that import { O, M, L } from "ts-toolbelt";
export default function deepAssign<
T extends { [key: string]: any },
U extends Array<{ [key: string]: any }>
>(
target: T,
...sources: U
): O.Assign<T, U, "deep", 1, M.BuiltInObject | L.List>;
export default function deepAssign(target: Record<string, any>) {
const sources = Array.prototype.slice.call(arguments, 1);
sources.forEach((source) => {
for (const key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
const value = source[key];
typeof value === "object" /* TODO: Check for "record", too */ &&
value /* !== null */ &&
Object instanceof (value.constructor || /* Returns false: */ deepAssign)
? deepAssign(target[key], value) // Extend object literals only
: (target[key] = value); // Treat non-literals like `Date` atomically
}
}
});
return target;
}
const x0 = deepAssign({}, { a: { b: { c: 1 } } }, { a: { b: { c: 2, d: 3 } } });
const x1 = deepAssign(
{},
{ a: { b: { c: 1 } } },
{ a: { b: { c: undefined, d: 3 } } }
);
const x5 = deepAssign({}, { a: [1, 2, 3] as const }, { a: [1] as const });
const x5b = deepAssign(
{},
{ a: [1, 2, 3] as const, b: "This string gets discarded" },
{ a: [1] as const }
);
const x6 = deepAssign({}, [], { a: 1 });
const x9 = deepAssign(
{ arr: [1, 2, 3] as const },
{ arr: [4] as const, b: null },
{ b: "test", c: { d: new Date() } },
{ c: { d: { e: "Not merged with date properties" } } }
);
|
Will look into this soon! Thanks for reporting 👍 Easy fix |
That's weird. As I've reinstalled my dependencies, I cannot reproduce it, either. Sorry for the false positive and thank you for being so helpful again! 😊 |
Sometimes it happens that ts gets "buggy" while editing complex types, as it tries to evaluate the type before it's even complete (eg. |
🤔 Question
Describe your question
I'm currently working on a
deepAssign
function which callsObject.assign
recursively on object literals (but not arrays or instantiated classes likeDate
). The header looks as follows:I would like the following object:
To match the following type:
How should this be done?
Search tags, topics
#deepAssign #deepMerge #object
The text was updated successfully, but these errors were encountered: