根据特定键的预定义数据类型转换字符串值的 Typescript 函数
Typescript function which casts string value based on predefined datatype for particular keys
编辑:我已经更新了问题底部的工作代码。
我有一个名为 get
的函数,它接受一个 key
作为参数和 returns 对应
值。
此函数为每个 key
.
维护 value
( 始终为字符串 )和 datatype
喜欢:
key value(stored as string) dataType
-------------------------------------------------
count '10' number
name 'foo' string
isHoliday '1' boolean
===== more rows here =====
在 returning 值转换为指定数据类型之前。
例如:
- 当键为
count
时,其值 '10'
被转换为数据类型 number
,值为 10
- 当键为
isHoliday
时,其值 '1'
被转换为数据类型 boolean
,值为 true
get
函数 return 具有正确类型的正确值。
但是 return typescript 在 funciton called 地方没有正确推断出类型。const count: number = get('count')
并产生类型错误,如:Type 'string | number | boolean' is not assignable to type 'number'.
代码:
type TypeName = 'number' | 'string' | 'boolean'
type ItemKey = 'count' | 'name' | 'isHoliday'
function get(key: ItemKey) {
/**
* Predefined data
*/
const data: {
[key: string]: {
value: string
dataType: TypeName
}
} = {
count : { value: '10', dataType: 'number' },
name : { value: 'foo', dataType: 'string' },
isHoliday : { value: '1', dataType: 'boolean' },
// ===== more items here =====
}
/**
* Fetch data based on input
*/
const value = data[key].value // fetch value
const dataType = data[key].dataType // fetch TypeName
/**
* Cast data type and return value
*/
if(dataType === 'number') {
return Number(value)
} else if(dataType === 'string') {
return String(value)
} else if(dataType === 'boolean') {
return value === '1'
}
}
/**
* Using get() funciton here
*/
const count: number = get('count')
// Error: Type 'string | number | boolean' is not assignable to type 'number'.
const isHoliday: boolean = get('isHoliday')
// Error: Type 'string | number | boolean' is not assignable to type 'boolean'.
我试过了。
但是我没有转换数据类型。
interface TypeRegistry {
count: number
name: string
isHoliday: boolean
// more items here
}
function get<K extends keyof TypeRegistry>(key: K): TypeRegistry[K] {
const data: {
[key: string]: {
value: string
}
} = {
count : { value: '10' },
name : { value: 'foo' },
isHoliday : { value: '1' },
// more items here
}
const value = data[key].value // fetch value
// How to cast value to TypeRegistry[K]???
return value
// Error: Type 'string' is not assignable to type 'TypeRegistry[K]'.
}
/**
* Using get() funciton here
*/
const count: number = get('count')
const isHoliday: boolean = get('isHoliday')
使用条件类型,但他们使用 key
来决定数据类型。
但在我的情况下,键的数量可能会有所不同。
我希望只使用 TypeNames 而不是键就可以工作的东西。
但这种方法也没有成功
代码:
type TypeName = 'number' | 'string' | 'boolean'
type ItemKey = 'count' | 'name' | 'isHoliday'
type ReturnDataType<T> =
T extends 'count' ? number :
T extends 'name' ? string :
T extends 'isHoliday' ? boolean:
// more items here
never;
function get<T extends ItemKey>(key: ItemKey): ReturnDataType<T> {
/**
* Predefined data
*/
const data: {
[key: string]: {
value: string
dataType: TypeName
}
} = {
count : { value: '10', dataType: 'number' },
name : { value: 'foo', dataType: 'string' },
isHoliday : { value: '1', dataType: 'boolean' },
// more items here
}
/**
* Fetch data based on input
*/
const value = data[key].value // fetch value
const dataType = data[key].dataType // fetch TypeName
/**
* Cast data type and return value
*/
if(dataType === 'number') {
return Number(value) as ReturnDataType<T>
} else if(dataType === 'string') {
return String(value) as ReturnDataType<T>
} else if(dataType === 'boolean') {
return Boolean(value) as ReturnDataType<T>
}
}
/**
* Using get() funciton here
*/
const count: number = get('count')
// Error:
// Type 'string | number | boolean' is not assignable to type 'number'.
// Type 'string' is not assignable to type 'number'.ts(2322)
const isHoliday: boolean = get('isHoliday')
// Error:
// Type 'string | number | boolean' is not assignable to type 'boolean'.
// Type 'string' is not assignable to type 'boolean'.ts(2322)
编辑:来自 jcalz ,我想出了以下代码并且它工作正常
/**
* Data type map
* Usefull only at compile time
*/
type DataTypeMap = {
'number': number
'string': string
'boolean': boolean
}
/**
* For typescript type hint (compile time)
*/
type TsTypeRegistry = {
count : number
name : string
isHoliday : boolean
// ===== more items here =====
}
/**
* Type required at runtime
*/
const JsTypeRegistry: {
[key: string]: keyof DataTypeMap
} = {
count : 'number',
name : 'string',
isHoliday : 'boolean',
// ===== more items here =====
}
/**
* Convert datatype at runtime
*/
function coerce(value: string, type: keyof DataTypeMap) {
switch(type) {
case 'number': return Number(value)
case 'string': return value
case 'boolean': return value === '1'
default: throw new Error(`coerce not implemented for type: '${type}'`)
}
}
/**
* The get function
*/
function get<K extends keyof TsTypeRegistry>(key: K): TsTypeRegistry[K] {
const data = {
count: { value: '10' },
name: { value: 'foo' },
isHoliday: { value: '1' },
// ===== more items here =====
}
const value = data[key].value
const dataType = JsTypeRegistry[key] // datatype required at runtime
return coerce(value, dataType) as TsTypeRegistry[K]
/**
* Here 'coerce' converts value at runtime
* and 'TsTypeRegistry[K]' gives correct type at compile time
*/
}
/**
* Using get function
*/
const count: number = get('count')
console.log(count, typeof count) // 10 'number'
const someName: string = get('name')
console.log(someName, typeof someName) // foo 'string'
const isHoliday: boolean = get('isHoliday')
console.log(isHoliday, typeof isHoliday) // true 'boolean
/**
* Now the get function converts data type at runtime as well as
* gives correct type hint at compile time.
*/
看起来你的各种代码示例都在做你想做的事情,但你没有将它们整合到一个单一的功能版本中。您需要在运行时强制类型(如您的第一个示例) 和 要么在编译时断言类型(如您的第二个示例),或者您可以重构为一个版本编译器实际上可以验证类型。
TypeScript 和 JavaScript 都没有真正按照您的想法进行“转换”。在 JavaScript 中,有时可能 coerce a value from one type to another, such as taking a string like "123"
and coercing it to a number by using it in an operation that expects a number like +"123"
. And in TypeScript it is possible to assert 值在运行时是特定类型,但这只是向编译器提供更多信息,并没有运行时效果。类型强制和类型断言在某些方面都可以被认为是“强制转换”,但它们有很大的不同,因此最好避免歧义,不要使用术语“强制转换”。
认识到类型强制和类型断言彼此完全无关是非常重要的。您添加到 TypeScript 的类型信息不会在运行时产生任何影响;注释或断言类型只会影响您看到的编译器警告类型。当 TypeScript 代码被编译为 JavaScript 时,TypeScript 的整个静态类型系统是 erased。如果您希望某个值在运行时属于特定类型,则需要编写一些代码来实现。
如果编译器无法确定您正在做的事情是类型安全的,例如在您的第一个示例中 string | number | boolean
不被视为可分配给 TypeRegistry[K]
,您可以使用类型断言或类似的东西来抑制编译器错误。对于像您这样的情况,其中一个函数有多个 return
行并且其中 none 行类型检查,我通常将函数编写为具有单个调用签名的 overloaded function。调用签名的类型足够强,允许调用者为不同的输入类型获得不同的输出类型,而实现签名的类型足够松散,允许 string | number | boolean
return 值:
interface TypeRegistry {
count: number
name: string
isHoliday: boolean
// more items here
}
// call signature
function get<K extends keyof TypeRegistry>(key: K): TypeRegistry[K];
// implementation
function get(key: keyof TypeRegistry): TypeRegistry[keyof TypeRegistry] {
const data = {
count: { value: '10', dataType: 'number' },
name: { value: 'foo', dataType: 'string' },
isHoliday: { value: '1', dataType: 'boolean' },
// ===== more items here =====
}
const value = data[key].value // fetch value
const dataType = data[key].dataType // fetch TypeName
if (dataType === 'number') {
return Number(value);
} else if (dataType === 'string') {
return String(value);
} else if (dataType === 'boolean') {
return value === '1';
}
throw new Error()
}
您可以验证这是否按预期工作。请注意,重载或类型断言需要您多加注意;编译器无法验证实现的类型安全性,我们也没有通过使用重载来改变这一点。我们所做的只是告诉编译器不要担心它。如果我们犯了一个错误(比如切换 dataType === 'number'
检查和 dataType === 'string'
检查),编译器不会注意到,你会在运行时看到问题。
如果您希望编译器在不需要断言或重载的情况下实际验证类型安全,您可以重构为如下内容:
const coerce = (v: string) => ({
get count() {
return +v
},
get name() {
return v
},
get isHoliday() {
return v === "1"
}
})
type TypeRegistry = ReturnType<typeof coerce>;
function get<K extends keyof TypeRegistry>(key: K): TypeRegistry[K] {
const data = {
count: { value: '10' },
name: { value: 'foo' },
isHoliday: { value: '1' },
// more items here
}
return coerce(data[key].value)[key];
}
/**
* Using get() funciton here
*/
const count: number = get('count');
console.log(typeof count); // "number"
const isHoliday: boolean = get('isHoliday')
console.log(typeof isHoliday) // "boolean"
我们仍在运行时强制转换值,但现在编译器可以跟随我们正在做的事情。这是可行的,因为 coerce()
函数的输出被视为 TypeRegistry
类型的值...它恰好是用 getters 实现的,因此实际上只调用了一个强制代码路径.并且编译器确实理解使用 K
类型的键索引到 TypeRegistry
类型的值将产生 TypeRegistry[K]
.
类型的值
编辑:我已经更新了问题底部的工作代码。
我有一个名为 get
的函数,它接受一个 key
作为参数和 returns 对应
值。
此函数为每个 key
.
维护 value
( 始终为字符串 )和 datatype
喜欢:
key value(stored as string) dataType
-------------------------------------------------
count '10' number
name 'foo' string
isHoliday '1' boolean
===== more rows here =====
在 returning 值转换为指定数据类型之前。
例如:
- 当键为
count
时,其值'10'
被转换为数据类型number
,值为10
- 当键为
isHoliday
时,其值'1'
被转换为数据类型boolean
,值为true
get
函数 return 具有正确类型的正确值。
但是 return typescript 在 funciton called 地方没有正确推断出类型。const count: number = get('count')
并产生类型错误,如:Type 'string | number | boolean' is not assignable to type 'number'.
代码:
type TypeName = 'number' | 'string' | 'boolean'
type ItemKey = 'count' | 'name' | 'isHoliday'
function get(key: ItemKey) {
/**
* Predefined data
*/
const data: {
[key: string]: {
value: string
dataType: TypeName
}
} = {
count : { value: '10', dataType: 'number' },
name : { value: 'foo', dataType: 'string' },
isHoliday : { value: '1', dataType: 'boolean' },
// ===== more items here =====
}
/**
* Fetch data based on input
*/
const value = data[key].value // fetch value
const dataType = data[key].dataType // fetch TypeName
/**
* Cast data type and return value
*/
if(dataType === 'number') {
return Number(value)
} else if(dataType === 'string') {
return String(value)
} else if(dataType === 'boolean') {
return value === '1'
}
}
/**
* Using get() funciton here
*/
const count: number = get('count')
// Error: Type 'string | number | boolean' is not assignable to type 'number'.
const isHoliday: boolean = get('isHoliday')
// Error: Type 'string | number | boolean' is not assignable to type 'boolean'.
我试过了
但是我没有转换数据类型。
interface TypeRegistry {
count: number
name: string
isHoliday: boolean
// more items here
}
function get<K extends keyof TypeRegistry>(key: K): TypeRegistry[K] {
const data: {
[key: string]: {
value: string
}
} = {
count : { value: '10' },
name : { value: 'foo' },
isHoliday : { value: '1' },
// more items here
}
const value = data[key].value // fetch value
// How to cast value to TypeRegistry[K]???
return value
// Error: Type 'string' is not assignable to type 'TypeRegistry[K]'.
}
/**
* Using get() funciton here
*/
const count: number = get('count')
const isHoliday: boolean = get('isHoliday')
key
来决定数据类型。
但在我的情况下,键的数量可能会有所不同。
我希望只使用 TypeNames 而不是键就可以工作的东西。
但这种方法也没有成功
代码:
type TypeName = 'number' | 'string' | 'boolean'
type ItemKey = 'count' | 'name' | 'isHoliday'
type ReturnDataType<T> =
T extends 'count' ? number :
T extends 'name' ? string :
T extends 'isHoliday' ? boolean:
// more items here
never;
function get<T extends ItemKey>(key: ItemKey): ReturnDataType<T> {
/**
* Predefined data
*/
const data: {
[key: string]: {
value: string
dataType: TypeName
}
} = {
count : { value: '10', dataType: 'number' },
name : { value: 'foo', dataType: 'string' },
isHoliday : { value: '1', dataType: 'boolean' },
// more items here
}
/**
* Fetch data based on input
*/
const value = data[key].value // fetch value
const dataType = data[key].dataType // fetch TypeName
/**
* Cast data type and return value
*/
if(dataType === 'number') {
return Number(value) as ReturnDataType<T>
} else if(dataType === 'string') {
return String(value) as ReturnDataType<T>
} else if(dataType === 'boolean') {
return Boolean(value) as ReturnDataType<T>
}
}
/**
* Using get() funciton here
*/
const count: number = get('count')
// Error:
// Type 'string | number | boolean' is not assignable to type 'number'.
// Type 'string' is not assignable to type 'number'.ts(2322)
const isHoliday: boolean = get('isHoliday')
// Error:
// Type 'string | number | boolean' is not assignable to type 'boolean'.
// Type 'string' is not assignable to type 'boolean'.ts(2322)
编辑:来自 jcalz
/**
* Data type map
* Usefull only at compile time
*/
type DataTypeMap = {
'number': number
'string': string
'boolean': boolean
}
/**
* For typescript type hint (compile time)
*/
type TsTypeRegistry = {
count : number
name : string
isHoliday : boolean
// ===== more items here =====
}
/**
* Type required at runtime
*/
const JsTypeRegistry: {
[key: string]: keyof DataTypeMap
} = {
count : 'number',
name : 'string',
isHoliday : 'boolean',
// ===== more items here =====
}
/**
* Convert datatype at runtime
*/
function coerce(value: string, type: keyof DataTypeMap) {
switch(type) {
case 'number': return Number(value)
case 'string': return value
case 'boolean': return value === '1'
default: throw new Error(`coerce not implemented for type: '${type}'`)
}
}
/**
* The get function
*/
function get<K extends keyof TsTypeRegistry>(key: K): TsTypeRegistry[K] {
const data = {
count: { value: '10' },
name: { value: 'foo' },
isHoliday: { value: '1' },
// ===== more items here =====
}
const value = data[key].value
const dataType = JsTypeRegistry[key] // datatype required at runtime
return coerce(value, dataType) as TsTypeRegistry[K]
/**
* Here 'coerce' converts value at runtime
* and 'TsTypeRegistry[K]' gives correct type at compile time
*/
}
/**
* Using get function
*/
const count: number = get('count')
console.log(count, typeof count) // 10 'number'
const someName: string = get('name')
console.log(someName, typeof someName) // foo 'string'
const isHoliday: boolean = get('isHoliday')
console.log(isHoliday, typeof isHoliday) // true 'boolean
/**
* Now the get function converts data type at runtime as well as
* gives correct type hint at compile time.
*/
看起来你的各种代码示例都在做你想做的事情,但你没有将它们整合到一个单一的功能版本中。您需要在运行时强制类型(如您的第一个示例) 和 要么在编译时断言类型(如您的第二个示例),或者您可以重构为一个版本编译器实际上可以验证类型。
TypeScript 和 JavaScript 都没有真正按照您的想法进行“转换”。在 JavaScript 中,有时可能 coerce a value from one type to another, such as taking a string like "123"
and coercing it to a number by using it in an operation that expects a number like +"123"
. And in TypeScript it is possible to assert 值在运行时是特定类型,但这只是向编译器提供更多信息,并没有运行时效果。类型强制和类型断言在某些方面都可以被认为是“强制转换”,但它们有很大的不同,因此最好避免歧义,不要使用术语“强制转换”。
认识到类型强制和类型断言彼此完全无关是非常重要的。您添加到 TypeScript 的类型信息不会在运行时产生任何影响;注释或断言类型只会影响您看到的编译器警告类型。当 TypeScript 代码被编译为 JavaScript 时,TypeScript 的整个静态类型系统是 erased。如果您希望某个值在运行时属于特定类型,则需要编写一些代码来实现。
如果编译器无法确定您正在做的事情是类型安全的,例如在您的第一个示例中 string | number | boolean
不被视为可分配给 TypeRegistry[K]
,您可以使用类型断言或类似的东西来抑制编译器错误。对于像您这样的情况,其中一个函数有多个 return
行并且其中 none 行类型检查,我通常将函数编写为具有单个调用签名的 overloaded function。调用签名的类型足够强,允许调用者为不同的输入类型获得不同的输出类型,而实现签名的类型足够松散,允许 string | number | boolean
return 值:
interface TypeRegistry {
count: number
name: string
isHoliday: boolean
// more items here
}
// call signature
function get<K extends keyof TypeRegistry>(key: K): TypeRegistry[K];
// implementation
function get(key: keyof TypeRegistry): TypeRegistry[keyof TypeRegistry] {
const data = {
count: { value: '10', dataType: 'number' },
name: { value: 'foo', dataType: 'string' },
isHoliday: { value: '1', dataType: 'boolean' },
// ===== more items here =====
}
const value = data[key].value // fetch value
const dataType = data[key].dataType // fetch TypeName
if (dataType === 'number') {
return Number(value);
} else if (dataType === 'string') {
return String(value);
} else if (dataType === 'boolean') {
return value === '1';
}
throw new Error()
}
您可以验证这是否按预期工作。请注意,重载或类型断言需要您多加注意;编译器无法验证实现的类型安全性,我们也没有通过使用重载来改变这一点。我们所做的只是告诉编译器不要担心它。如果我们犯了一个错误(比如切换 dataType === 'number'
检查和 dataType === 'string'
检查),编译器不会注意到,你会在运行时看到问题。
如果您希望编译器在不需要断言或重载的情况下实际验证类型安全,您可以重构为如下内容:
const coerce = (v: string) => ({
get count() {
return +v
},
get name() {
return v
},
get isHoliday() {
return v === "1"
}
})
type TypeRegistry = ReturnType<typeof coerce>;
function get<K extends keyof TypeRegistry>(key: K): TypeRegistry[K] {
const data = {
count: { value: '10' },
name: { value: 'foo' },
isHoliday: { value: '1' },
// more items here
}
return coerce(data[key].value)[key];
}
/**
* Using get() funciton here
*/
const count: number = get('count');
console.log(typeof count); // "number"
const isHoliday: boolean = get('isHoliday')
console.log(typeof isHoliday) // "boolean"
我们仍在运行时强制转换值,但现在编译器可以跟随我们正在做的事情。这是可行的,因为 coerce()
函数的输出被视为 TypeRegistry
类型的值...它恰好是用 getters 实现的,因此实际上只调用了一个强制代码路径.并且编译器确实理解使用 K
类型的键索引到 TypeRegistry
类型的值将产生 TypeRegistry[K]
.