Home > Back-end >  How to create string literal type from interface key and key value
How to create string literal type from interface key and key value

Time:07-13

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:

  1. 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
  2. Not inserting required key doesn't get an error
    • const payload: CondensedPayload = '<payload><email>[email protected]</email></payload>' // no error
  3. 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
  4. 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.

Playground

  • Related