Hey everyone! Wondering about a way to type a “configurable” object containing methods based on an input object.
For example, given a configuration like the following:
const config = [
{ name: 'A', fn: () => 'Hello' },
{ name: 'B', fn: (n: number) => n * 2 },
]
We would return an API with an interface like:
interface API {
A: () => 'Hello';
B: (n: number) => number;
}
The mapping could happen something like this:
const makeAPI = <T extends Something?>(config: T) => {
const api = {} // as SomethingElse<T>?
config.forEach(obj => { api[obj.name] = obj.fn })
return api
}
Is it possible to type something like this, so that the function types are tied to their names?
const config = [
{name: 'A', fn: () => 'Hello'},
{name: 'B', fn: (n: number) => n * 2},
] as const
type Config = typeof config
type API = U.IntersectOf<O.UnionOf<{
[K in Exclude<keyof Config, keyof any[]>]: O.Record<O.At<Config[K], 'name'>, O.At<Config[K], 'fn'>>
}>>
Exclude
generic operate on those keys? Is it just excluding all the array prototype methods? So, keys 0
, 1
are left, because they are not known to exist on any[]
?
So great, needed a couple of tweaks, but managed to get this working as a generic:
type API<Config> = U.IntersectOf<
O.UnionOf<
{
[K in Exclude<keyof Config, keyof any[]>]: Config[K] extends {
name: A.Key;
fn: F.Function;
}
? O.Record<O.At<Config[K], "name">, O.At<Config[K], "fn">>
: never;
}
>
>;
Thanks again!
I have a structure; either an Array, or an Object, and then recursively more Arrays, Objects, with primitives etc,
but also with fp-ts's O.Option<T>'s, I want to replace all the Option<T>'s with T | null, recursively not only within Arrays or Objects, but also inside the Option<T>'s themselves; as they may also contain arrays or objects etc.
so far any of my trials result in Typescript bombing out because I make circular references in my types...
anyone any idea if the toolbelt has the right tools for the job?
hi, thanks @pirix-gh:
type SomeObject = {
a: { b: O.Option<string> },
c: { d: Array<O.Option<{e: O.Option<boolean>}>> }
}
Convert that to:
type ConvertedSomeObject = {
a: { b: string | null },
c: { d: Array<{ e: boolean | null } | null> }
}
similarly:type X = Array<SomeObject>
->type ConvertedX = Array<ConvertedSomeObject>
similarly:type Y = O.Option<{ a: O.Option<string> }>
->type ConvertedY = { a: string | null } | null
but the Object or Array as root are the more important cases.
so: Replace all O.Option<T>
with T | null
const encodeOptionsAsNullable = (value: any, cacheMap: Map<any, any>): any => {
const cacheEntry = cacheMap.get(value)
if (cacheEntry) {
return cacheEntry
}
if (Array.isArray(value)) {
const newAr: typeof value = []
cacheMap.set(value, newAr)
value.forEach((x) => newAr.push(encodeOptionsAsNullable(x, cacheMap)))
return newAr
}
if (value instanceof Object) {
if (value._tag === "Some" || value._tag === "None") {
return encodeOptionsAsNullable(O.toNullable(value), cacheMap)
}
const newObj = {} as Record<string, any>
cacheMap.set(value, newObj)
Object.keys(value).forEach((key) => {
newObj[key] = encodeOptionsAsNullable(value[key], cacheMap)
}, newObj)
return newObj
}
return value
}
@patroza this should do, you're welcome to ask questions
import {C, U} from 'ts-toolbelt'
import {Option, Some, None} from 'fp-ts/lib/Option'
type SomeObject = {
0: Option<string>,
a: { b: Option<string> },
c: { d: Array<Option<{e: Option<boolean>}>> }
}
type OptionOf<A> =
U.Exclude<
A extends Some<infer X>
? X | null
: A,
None
>
type Transform<O> = {
[K in keyof O]: OptionOf<O[K]> extends infer X
? X extends (infer Y)[]
? OptionOf<Transform<Y>>[]
: X extends object
? Transform<X>
: X
: never
} & {}
type test0 = Transform<SomeObject>
Remove the & {}
if you plan to work on nested types.
it looks like Date is coming out strangely, I made an exclusion like this:
type Passthrough = Date
export type Transform<O> = {
[K in keyof O]: OptionOf<O[K]> extends infer X
? X extends (infer Y)[]
? OptionOf<Transform<Y>>[]
: X extends Passthrough
? X
: X extends object
? Transform<X>
: X
: never
} & {}
To resolve this:Type '{ createdAt: { toString: {}; toDateString: {}; toTimeString: {}; toLocaleString: {}; toLocaleDateString: {}; toLocaleTimeString: {}; valueOf: {}; getTime: {}; getFullYear: {}; getUTCFullYear: {}; ... 32 more ...; toJSON: {}; }; ... 12 more ...;
Ouch, thanks for pointing this out. This is a problem because Date
gets resolved as object
, like Regexp or Promise. We should filter out all the std built-in objects to avoid this. That's my modified version.
type Transform<O> =
O extends Date | RegExp | Function | Promise<any> ? O : {
[K in keyof O]: OptionOf<O[K]> extends infer X
? X extends (infer Y)[]
? OptionOf<Transform<Y>>[]
: Transform<X>
: never
}
This is definitely something I should check on the ts-toolbelt. I think I'll have to create a "built-in type" so that we can do O extends BuiltInObject ? ...
. Please note that the modified version does not integrate all buil-in types (bigint), I should work on this!
And I also excluded Function
because mapping over it makes it become an empty object.
Merci beaucoup! very nice. good point regarding function & Promise.
in graphql these may be valid types too, but I think any function or Promise should be it's own "root" again and apply conversion accordingly.
I personally don't have the usecase for it right now.
A built-in types type sounds good.
Transform
to a more meaningful name then you're all set. Here's my (more complicated) version ^^:type Transform<O, _O = OptionOf<O>> =
_O extends M.BuiltInObject ? _O : {
[K in keyof _O]: OptionOf<_O[K]> extends infer X
? X extends (infer Y)[]
? OptionOf<Transform<Y>>[]
: Transform<X>
: never
}
DeepMerge
type working? Specifically when you have arrays along the way. See this (broken) example: https://codesandbox.io/s/pensive-franklin-3xn39?file=/src/index.ts:239-264
Pick
can be found in the Union
module. In actuality, I find it in the Object
module.
import { A, M } from 'ts-toolbelt';
import { Depth } from 'Object/_Internal';
type ComputeFlat<A extends any> = A extends M.BuiltInObject
? A
: A extends Array<any>
? A extends Array<Record<string | number | symbol, any>>
? Array<
{
[K in keyof A[number]]: A[number][K];
} & {}
>
: A
: A extends ReadonlyArray<any>
? A extends ReadonlyArray<Record<string | number | symbol, any>>
? ReadonlyArray<
{
[K in keyof A[number]]: A[number][K];
} & {}
>
: A
: {
[K in keyof A]: A[K];
} & {};
type ComputeDeep<A extends any> = A extends M.BuiltInObject
? A
: A extends Array<any>
? A extends Array<Record<string | number | symbol, any>>
? Array<
{
[K in keyof A[number]]: ComputeDeep<A[number][K]>;
} & {}
>
: A
: A extends ReadonlyArray<any>
? A extends ReadonlyArray<Record<string | number | symbol, any>>
? ReadonlyArray<
{
[K in keyof A[number]]: ComputeDeep<A[number][K]>;
} & {}
>
: A
: {
[K in keyof A]: ComputeDeep<A[K]>;
} & {};
export type Compute<A extends any, depth extends Depth = 'deep'> = {
flat: ComputeFlat<A>;
deep: ComputeDeep<A>;
}[depth];
type test = {
a: ReadonlyArray<{
a: number;
c: ReadonlyArray<{
a: number;
}>;
}>;
b: { a: number };
} & {
a: ReadonlyArray<{
d: Array<{
b: number;
}>;
c: ReadonlyArray<{
b: number;
}>;
}>;
c: { c: number };
};
// fixed variant (or I think it's fixed)
type a = Compute<test>;
// ts-toolbelt one
type c = A.Compute<test>;
Hello ! Im trying to rename all the keys in an interface using ts-toolbelt
from this
{
findX: ()=> X
}
to this
{
getX: ()=>X
}
I have the rename method :
const method = "findHello";
type FindToGet<Method> = `get${Method extends `find${infer X}` ? X : never}`;
const methodGet: FindToGet<typeof method> = "getHello";
And now I need to "apply" it on every key. I thought ts-toolbelt could help me but I dont find the function in Object
.
Any pointers ?
Hi folks, I'm relatively new to writing ts types and need some direction. I keep having the problem that I want to recurse through an object, transforming it, meaning that I want to pick only some properties and use them in the property I'm defining. I have found a way to achieve the result I want, but ts is sometimes complaining about complexity so I figure there is a better way with a trick I don't know. Consider this working example below. Importantly the str
does not contain Id's or Haba, which is what I want.
However, I feel like I should be able to filter the keyof T inside the "object" instead of having to wait and do it outside using O.Filter. So, I had a look at the internals of O.Filter, and realize that the trick I want might be there. Could you explain how _FilterKeys works? I don't understand the syntax.
export declare type _FilterKeys<O extends object, M extends any, match extends Match> = {
[K in keyof O]-?: {
1: never;
0: K;
}[Is<O[K], M, match>];
}[keyof O];
type Rec = Record<keyof any, any>;
/**
* Narrow T to its structural properties (Rec and []).
*/
export type Structure<T> = O.Filter<{
[K in keyof T]?: T[K] extends Array<infer I>
? Structure<I>
: T[K] extends Rec
? Structure<T[K]>
: undefined
}, undefined>;
const document = {
Approval: {
Haba: 'haba',
Steps: [
{
Id: 'ste1',
Groups: [
{
Id: 'gro1',
Actions: [ { Id: 'act1' } ],
},
],
},
],
},
};
const str: Structure<typeof document> = {
Approval: {
Steps: {
Groups: {
Actions: {},
},
},
},
};