Suppose we have an interface and enum
enum Destination = {
Home = 'home',
Cafe = 'cafe'
}
interface Payload {
apiVersion: string;
destination: Destination;
email?: string;
}
And I want to create a template literal type that takes the interface key as the opening and closing tag, and the key value as the tag value.
const condensedPayload: CondensedPayload = '<payload><apiVersion>2.1</apiVersion><destination>home</destination><email>[email protected]</email></payload>'
I've tried to create CondensedPayload
like this
type CondensedPayload = `<payload><${keyof Payload}>${Payload[keyof Payload]}</${keyof Payload}></payload>`
The result:
- The closing tag name which is not the same as the opening tag name or vice versa, doesn't get an error
const payload: CondensedPayload = '<payload><apiVersion>2.0</destination></payload>' // no error
- Not inserting required key doesn't get an error
const payload: CondensedPayload = '<payload><email>[email protected]</email></payload>' // no error
- Inserted only one required key doesn't get an error
const payload: CondensedPayload = '<payload><apiVersion>2.0</apiVersion><email>[email protected]</email></payload>' // no error
- Insert string outside
Destination
enum on<destination>some string</destination>
doesn't get an error
Is there a way to match the opening, closing, and value like maybe iterate the keyof Payload
and value type?
CodePudding user response:
Is it possible? Yes. Should you do it? Probably not...
The following solution will generate all possible combinations of string literal types which are valid for a given Payload
type.
type Expand<T> = T extends infer U ? { [K in keyof U]: U[K] } : never
type OptionalProps<T extends object> = Exclude<{
[K in keyof T]: T extends Record<K, T[K]>
? never
: K
}[keyof T], undefined>
type AllPermutations<T extends string, O extends string> = [T] extends [keyof T] ? [] : {
[K in T]:
K extends O
? [
...(AllPermutations<Exclude<T, K>, O> extends infer U extends string[]
? U
: never)
]
| [
K, ...(AllPermutations<Exclude<T, K>, O> extends infer U extends string[]
? U
: never)
]
: [
K, ...(AllPermutations<Exclude<T, K>, O> extends infer U extends string[]
? U
: never)
]
}[T]
type Join<T extends string[]> =
T extends [infer L extends string, ...infer R extends string[]]
? `${L}${Join<R>}`
: ""
type CreatePayloadString<T extends string[], P extends Record<string, any>> =
T extends [infer L extends string, ...infer R extends string[]]
? `<${L}>${P[L & keyof P]}</${L}>${CreatePayloadString<R, P>}`
: ""
type CondensedPayload = {
[K in AllPermutations<keyof Payload, OptionalProps<Payload>> as Join<K>]:
`<payload>${CreatePayloadString<K, Payload>}</payload>`
} extends infer O ? O[keyof O] : never
Some tests:
// Error
const payload1: CondensedPayload = '<payload><apiVersion>2.0</destination></payload>'
const payload2: CondensedPayload = '<payload><email>[email protected]</email></payload>'
const payload3: CondensedPayload = '<payload><apiVersion>2.0</apiVersion><email>[email protected]</email></payload>'
const payload4: CondensedPayload = '<payload><apiVersion>abc</apiVersion><destination>some string</destination></payload>'
// Valid
const payload4: CondensedPayload = '<payload><apiVersion>abc</apiVersion><destination>home</destination></payload>'
But seriously, use an XML parser for stuff like this or even better: Don't use XML.