Add Replace<Type, Keys, Replacement> that will replace all keys "Keys" of type "Type" with "Replacement"
xenoterracide opened this issue · comments
I find myself wanting to do things like StrictReplaceType<Foo,BarConstructorOpts,'bar'>
where Foo
looks something like
class Foo {
bar: Bar
}
would be good to have a deep variant too, and I'm trying to create a deep constructor factory for both.
Found this on SO, and generall seems to do what I want (replaced Exclude with StrictExclude though I'm not certain it's really valuable here)
type Identity<T> = { [P in keyof T]: T[P] };
type Replace<T, K extends keyof T, TReplace> = Identity<
Pick<T, StrictExclude<keyof T, K>> & {
[P in K]: TReplace;
}
>;
@Beraliv I've got an implementation of this and a deep version. Not sure if I should work on a patch
import { Builtin, StrictExclude } from 'ts-essentials';
export type Identity<T> = { [P in keyof T]: T[P] };
export type Replace<T, K extends keyof T, TReplace> = Identity<
Pick<T, StrictExclude<keyof T, K>> & {
[P in K]: TReplace;
}
>;
export type DeepReplace<T, TReplace> = T extends Builtin
? TReplace
: T extends Map<infer K, infer V>
? Map<K, DeepReplace<V, TReplace>>
: T extends ReadonlyMap<infer K, infer V>
? ReadonlyMap<K, DeepReplace<V, TReplace>>
: T extends WeakMap<infer K, infer V>
? WeakMap<K, DeepReplace<V, TReplace>>
: T extends Set<infer U>
? Set<DeepReplace<U, TReplace>>
: T extends ReadonlySet<infer U>
? ReadonlySet<DeepReplace<U, TReplace>>
: T extends WeakSet<infer U>
? WeakSet<DeepReplace<U, TReplace>>
: T extends Promise<infer U>
? Promise<DeepReplace<U, TReplace>>
: T extends object
? { [K in keyof T]-?: DeepReplace<T[K], TReplace> }
: TReplace;
Hey @xenoterracide!
Apologies for the late response, let me quickly come back to you later today!
I will be able to properly test it and come back to you with the feedback!
no worries. I've realized I might have another use case this doesn't cover though, and I'm not sure you've got prior art for that
type Foo = DeepReplace<Bar, {
baz: Array<Bar>
}>
might be useful, but only with static keyname checking... otherwise you'd have to replace them one at a time
however, then that kind of conflicts with this
export interface Entity<Id> {
id: Id;
createdAt: Date;
modifiedAt: Date;
}
export type EntityOmit = DeepReplace<Entity<never>, never>;
should be possible to have an API something like
DeepReplace<Entity<never>, never, 'all'>
DeepReplace<Entity<never>, { createdAt: string }, 'filter'>
P.S. Happy to try creating a PR, I've just come to hate doing those until someone tells me they're interested due to the amount of work required. Current employer is letting me.
hmm... I think that implementation of Replace
might be replacing a readonly foo: readonly[]
to foo: []
when trying Replace<'foo', []>
which should result in readonly foo: []
(imho)
Hey again!
Thank you for the suggestions!
I'm currently looking at how it can fit in what we already have in ts-essentials.
So now we have DeepOmit
, DeepPick
which use DeepModify
and I would want to stick to this approach in case we want to introduce another DeepCandidate.
In this case the structure will be slightly different and there will be some limitations to the solution:
-
You need to keep the structure of the type you want to replace, e.g.
DeepReplace<ComplexObject, { simple: string; nested: { func: () => number }>
-
You won't be able to use self referencing types, e.g. recursive type
JSONValue
– https://tsplay.dev/wRze7w
hmm... I think that implementation of
Replace
might be replacing areadonly foo: readonly[]
tofoo: []
when tryingReplace<'foo', []>
which should result inreadonly foo: []
(imho)
The problem that you've mentioned about readonly
above is connected to the implementation as { [K in keyof T]-?: DeepReplace<T[K], TReplace> }
isn't homomorphic, you can read more about it here – https://stackoverflow.com/questions/59790508/what-does-homomorphic-mapped-type-mean/59791889#59791889
The implementation of Replace
itself can look similar, but you will need to explicitly say which fields you want assign to what – https://tsplay.dev/NV75lm. The reason I want to implement it this way as it allows you to replace fields with different values.
Do you think it will be useful in you case? Can you please add more examples where you find it useful?
I think it would be useful at least in some of the use cases.
However one of my use cases is to take a regular type or interface and turn it into the input for a DeepOmit, and providing never for every single thing doesn't feel very efficient. Which is why I was considering a more expansive API, if a full level of filter was really needed.
yeah, I was going to try DeepModify
then I realized it wasn't exported, so I couldn't play with it locally, and I didn't come into that immediately ;).
Maybe the never
case would be better resolved by a different type, like a Never<T>
and a DeepNever<T>
(not sure I have a case for a never that isn't all, I just haven't had a deep structure yet). I added the never case, but hopefully you can see why I don't think it's code-ly efficient.
I added one other example, as I spend most of my time replacing array types with NonEmptyArray
, or never
-ing.
should I patch this? should DeepNever
be a different type?
Apologies for the late response, let me come back to you later today
yeah, I was going to try
DeepModify
then I realized it wasn't exported, so I couldn't play with it locally, and I didn't come into that immediately
DeepModify
is not exported on purpose but it's a way to work with types like DeepOmit
and DeepPick
.
I added the never case, but hopefully you can see why I don't think it's code-ly efficient.
I understand your reasons, but it's not too scalable once you have edge cases, that's why I'm worried to use such an approach.
E.g. you have one never
that you want to replace and another one isn't really working for you. In types we don't have high order types (we kind of have HKTs but they're usually predefined so it's almost impossible to do something when you don't know about the type in advance) so we cannot have callbacks alternatives as in JavaScript.
That's why I suggest you to do what's already used in DeepOmit
and DeepPick
– you will need to repeat the structure, it will restrict and limit the usage but will increase scalability when you have more adjustments.
If you have simpler cases, e.g. export type EntityOmit = DeepReplace<Entity<never>, TSomethingElse>;
, you can simply use generic types instead, e.g. Entity<TSomethingElse>
export interface Entity<Id> { id: Id; createdAt: Date; modifiedAt: Date; } export type EntityOmit = DeepReplace<Entity<never>, never>;DeepReplace<Entity<never>, never, 'all'> DeepReplace<Entity<never>, { createdAt: string }, 'filter'>
Given these examples, could you please let me know what you expect from these examples? You've mentioned all
and filter
here, but it's not obvious what you want to get as an output?
P.S. Happy to try creating a PR, I've just come to hate doing those until someone tells me they're interested due to the amount of work required. Current employer is letting me.
Before jumping on the code I want to understand all the cases, it's still unclear for me. Can I ask you to write down the following please?
- Input, or what you try to change
- Manipulation, or how you want to change the input
- Expected result, or what you want to get at the end
Once we have it as a short list of examples, let's think of API and once it's agreed, I'm happy to start working on it
@xenoterracide Hey!
I think we got a second discussion and totally avoided the original suggestion about StrictReplaceType
Will it still be valuable for you?
If you want to focus on DeepReplace
instead, would it be okay to close this one as "won't do", could you please create a separate issue with all requirements that you need for that?
Thank you in advance!
@xenoterracide hey!
Can you please highlight the situations where Replace
would be helpful for you?
Because I think StrictOmit
(analogue of Omit
within ts-essentials) and Record
together will be a good implementation for your Replace
:
type Replace<Type, Keys extends keyof Type, TReplace> = StrictOmit<Type, Keys> & Record<Keys, TReplace>
So I think unless you disagree, I will close this issue