Repository navigation
Implementing DeepMerge #12769
Description
Activity
these two examples seem conflicting to me..
update( { a: { b: [1] }, c: 33, d: '44' }, { a: { b: [1, 2] }, d: '66' } )
and
update({ a: 33 }, { a: undefined })
since the first is more or less the same as saying
c: undefined.on any rate, i would say using two type parameters and a constraint to check their relationship would be a good way to go:
declare function update<T, U extends T>(a: T, U): void;
assuming that the first argument is a "spec" and the second can omit members, then it can be as:
declare function update<T, U extends T>(a: T, b:Partial<U>): void;
- addedQuestionAn issue which isn't directly actionable in codeAn issue which isn't directly actionable in code
on Dec 8, 2016 Not sure how that helps :(
Btw, did you mean T extends U ? I doesn't makes sense for U to have more properties than T in my usage.That's what I have so far btw :
type UpdateSpec<T> = { [P in keyof T]?: UpdateSpec<T[P]> } export default function update<T>(obj: T, spec: UpdateSpec<T>): T
i am confused now.. so if all what you want is for the second be a partial of the first, why now just:
declare function update<T>(a: T, b: Partial<T>): void;
Because it has to be deep. At every level, it is allowed to either pass a primitive (it will be set) or an object with a partial set of keys, and so on.
i.e, this should compile :
const updated = update( { a: { b: { c: 33, d: 33, e: 'aa' } } }, { a: { b: { c: 99 } } ) // updated is now equals to { a: { b: { c: 99, d: 33, e: 'aa' } } } // The spec didn't change d and e, so their values are still the same.
Made some progress with
type UpdateSpec<T> = { [P in keyof T]?: T[P] & UpdateSpec<T[P]> } export default function update<T>(obj: T, spec: UpdateSpec<T>): T
But I still get stack overflows for type inference on some usages and I wish the ? operator didn't allow explicitly setting undefined values.
This will be troublesome for React too, as setState() will allow one to set undefined on a non nullable property.
I do not think the intersection is correct.. this should work..
type UpdateSpec<T> = { [P in keyof T]?: UpdateSpec<T[P]> }
that it does not is seems like a bug, and we should investigate this further.
- addedBugA bug in TypeScriptA bug in TypeScriptand removedQuestionAn issue which isn't directly actionable in codeAn issue which isn't directly actionable in code
on Dec 8, 2016 Yeah it's strange. Without intersecting with T[P], I can update most properties with another property with a completely different type.
that is cause the recursion makes it go to
any. I will need to discuss this with Anders Hejlsberg (@ahejlsberg) and see if there is anything we can do to make this work in a better way.With the current nightly we are actually very close:
type UpdateSpec<T> = { [P in keyof T]?: UpdateSpec<T[P]> }; declare function update<T>(obj: T, spec: UpdateSpec<T>): T; declare let foo: { a: number, b: { x: string } }; // These all work as expected update(foo, { a: 1 }); update(foo, { b: { x: "test" } }); update(foo, { a: 10, b: { x: "howdy" } }); // These are all errors as expected update(foo, { a: "hello" }); update(foo, { b: { x: 42 } }); update(foo, { b: { y: 10 } }); update(foo, { b: { y: 10 } }); update(foo, { c: true }); // These should be errors but aren't update(foo, "ouch"); update(foo, { b: "ouch" });
The thing that works for us here is that when a homomorphic mapped type
{ [P in keyof T]: X }is instantiated for a primitive type we simply yield the primitive type. The thing that doesn't work so well is actually quite unrelated to mapped types: When the target of an assignment is a type with properties that are all optional, the source can effectively be any type that doesn't have those properties. For example:let x: { a?: number, b?: string } = "ouch";
We're perfectly happy with that because type
Stringhas noaorbproperty and therefore satisfies the contract. Since anUpdateSpec<T>has only optional properties we end up in the same situation.I see! Would it help with the primitive case if you could say "give me an object with the same shape , with all optional keys BUT with a cardinality of at least one"? After all, if you're willing to update something, you might as well not pass a null object.
So it gets closer to full type safety, but I have the feeling you're not willing to go to full typesafety considering the "primitive to fully optional {} is structurally allowed" (which makes sense in most use cases but is a bit unfortunate in this one); or is it that you know about an upcoming fix specially for Mapped types that could circumvent that?
Anyway, thanks; that's already much better than what can be done currently!
14 remaining items
- assigned and unassigned
on May 5, 2017 sandersn commented
on Jun 29, 2017 MemberMore actionsWith 2.4's weak type checking, the last two examples are now errors, so I think the last part of this bug has been addressed.
What about when there is a property missing from a nested object?
type UpdateSpec<T> = {[P in keyof T]?: T[P] & UpdateSpec<T[P]>}; declare function update<T>(obj: T, spec: UpdateSpec<T>): T; interface IFoo { a: number; b: {x: string; y: number}; c?: string; } var foo: IFoo = { a: 1, b: {x: "s", y: 1}, }; // Should not error update(foo, { a: 99, b: { y: 2 } });
but yields
Argument of type '{ a: number; b: { y: number; }; }' is not assignable to parameter of type 'UpdateSpec<IFoo>'. Types of property 'b' are incompatible. Type '{ y: number; }' is not assignable to type '({ x: string; y: number; } & UpdateSpec<{ x: string; y: number; }>) | undefined'. Type '{ y: number; }' is not assignable to type '{ x: string; y: number; } & UpdateSpec<{ x: string; y: number; }>'. Type '{ y: number; }' is not assignable to type '{ x: string; y: number; }'.should not it be
Partial<T[P]>instead ofT[P].- addedFixedA PR has been merged for this issueA PR has been merged for this issue
on Sep 18, 2017 Ah, you're right. Works with Partial, thanks!
- locked and limited conversation to collaborators
on Jun 19, 2018
Hello and good job on the mapped types!
Though it seems recursive mapped types didn't get as much love.
Is there any way to write the signature of
updateso that the following can be true?I run into either too permissive behaviors (I can add keys that are not in the original object, or set a non nullable key to undefined) or inferFromTypes: Maximum call stack size exceeded.