[Typescript challenge] 20. Medium - Chainable Options

Chainable options are commonly used in Javascript. But when we switch to TypeScript, can you properly type it?

In this challenge, you need to type an object or a class - whatever you like - to provide two function option(key, value) and get(). In option, you can extend the current config type by the given key and value. We should about to access the final result via get.

For example

declare const config: Chainable

const result = config
  .option('foo', 123)
  .option('name', 'type-challenges')
  .option('bar', { value: 'Hello World' })
  .get()

// expect the type of result to be:
interface Result {
  foo: number
  name: string
  bar: {
    value: string
  }
}

You don't need to write any js/ts logic to handle the problem - just in type level.

You can assume that key only accepts string and the value can be anything - just leave it as-is. Same key won't be passed twice.

 

Solution:

type Chainable<T = {}> = {
  option<K extends string, V>(key: K extends keyof T ? (V extends T[K] ? never: K): K, value: V): Chainable<Omit<T, K> & {[P in K]: V}>,
  get(): T
}

 

Test case:

/* _____________ Test Cases _____________ */
import type { Alike, Expect } from '@type-challenges/utils'
type x = 'a' extends 'a' ? true: false
declare const a: Chainable

const result1 = a
  .option('foo', 123)
  .option('bar', { value: 'Hello World' })
  .option('name', 'type-challenges')
  .get()

// If key is the same, and value is the same type
// then should expect error
const result2 = a
  .option('name', 'another name')
  // @ts-expect-error
  .option('name', 'last name')
  .get()

// If key is the same, but value is different type
// then should override the value with the same key
const result3 = a
  .option('name', 'another name')
  .option('name', 123)
  .get()

type cases = [
  Expect<Alike<typeof result1, Expected1>>,
  Expect<Alike<typeof result2, Expected2>>,
  Expect<Alike<typeof result3, Expected3>>,
]

type Expected1 = {
  foo: number
  bar: {
    value: string
  }
  name: string
}

type Expected2 = {
  name: string
}

type Expected3 = {
  name: number
}

 

Notice Test 2:

// If key is the same, and value is the same type
// then should expect error
const result2 = a
  .option('name', 'another name')
  // @ts-expect-error
  .option('name', 'last name')
  .get()

This means, we need to control key's type:

key: K extends keyof T ? (V extends T[K] ? never: K): K if K is already inside T, then check V is the same type of T[K], if they are the same type, should be never, otherwise keep K.
 
Notice Test 3:
// If key is the same, but value is different type
// then should override the value with the same key
const result3 = a
  .option('name', 'another name')
  .option('name', 123)
  .get()

type Expected3 = {
  name: number
}

The finial output is number type. Which means should override previous type.

Chainable<Omit<T, K> & {[P in K]: V}>

If you don't omit previous type, then it should be 

{
  name: string
} & {
  name: number
}

 

posted @   Zhentiw  阅读(73)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
历史上的今天:
2020-09-08 [CSS 3] content-visibility: auto Improve rendering performance
2020-09-08 [Machine Learning] Neural Networks: Representation Quiz
2018-09-08 [GraphQL] Query Local and Remote Data in Apollo Link State
2018-09-08 [Spring Boot ] Creating the Spring Boot Project : Demo: Creating a REST Controller
2018-09-08 [Web Analytics] Into to Web Analytics
2016-09-08 [AngularJS] Test an Angular Component with $componentController
2016-09-08 [AngularJS] Isolate State Mutations in Angular Components
点击右上角即可分享
微信分享提示