提问人:Kronos24 提问时间:9/25/2023 更新时间:9/27/2023 访问量:89
在 Typescript 中,是否可以检查泛型函数的返回对象类型的正确性?
In Typescript is it possible to check the correctnes of the return object type of a generic function?
问:
我试图了解是否有办法验证不同可能类型的 JSON.parse 结果。 在我正在处理的 API 中,有很多来自数据库的 Json 字段应该使用特定的结构,所以我想检查从数据库返回的某个 JsonValue 是否与给定类型一致。
我做了一些不同的尝试,这就是我定义通用功能的方式:
parseJsonField<T>(jsonValue: Prisma.JsonValue): T {
return JSON.parse(JSON.stringify(jsonValue));
}
当 jsonValue 没有 T 类型的确切属性时,我想有一些抛出错误的东西,但这不起作用。
我正在用这个类进行测试
export class ProgramTagsDTO {
key: string;
value: string;
constructor(key: string, value: string) {
this.key = key;
this.value = value;
}
}
当我运行此测试时:
it('should validate', () => {
const tags: Prisma.JsonValue = [
{
key: 'key1',
value: 'value1',
wrongField: 'value',
},
{
key: 'key2',
value: 'value2',
},
];
let result = null;
const expectedResult = [
new ProgramTagsDTO('key1', 'value1'),
new ProgramTagsDTO('key2', 'value2'),
];
try {
result = programmesService.parseJsonField<ProgramTagsDTO[]>(tags);
} catch (error) {
expect(error).toBeInstanceOf(Error);
}
expect(result).toEqual(expectedResult);
});
我得到:
expect(received).toEqual(expected) // deep equality
- Expected - 2
+ Received + 3
Array [
- ProgramTagsDTO {
+ Object {
"key": "key1",
"value": "value1",
+ "wrongField": "value",
},
- ProgramTagsDTO {
+ Object {
"key": "key2",
"value": "value2",
},
]
但这不是我想要的,我想让该方法抛出异常。(expect.toEqual 只是为了查看日志)
答:
0赞
Kronos24
9/27/2023
#1
经过几天的尝试,我和一位同事想出了这个解决方案,目前看来效果很好:
添加此导入:
import { ClassConstructor, plainToClass } from 'class-transformer';
import { validate } from 'class-validator';
我们为数组和单个值制作了 2 种方法:
public static async parseJsonArray<T>(
cls: ClassConstructor<T>,
json: Prisma.JsonValue,
): Promise<T[]> {
if (!Array.isArray(json)) {
throw new UnprocessableEntityException(
`Json value is not a ${cls.name}[]`,
);
}
const res: Promise<T>[] = [];
for (const element of json) {
res.push(this.parseJsonValue<T>(cls, element));
}
return Promise.all(res);
}
public static async parseJsonValue<T>(
cls: ClassConstructor<T>,
json: Prisma.JsonValue,
): Promise<T> {
const parsed = JSON.parse(JSON.stringify(json));
const transformed: T = plainToClass(cls, parsed, {
excludeExtraneousValues: true,
});
return validate(transformed as object).then((errors) => {
if (errors.length) {
throw new UnprocessableEntityException(
`Json value is not a ${cls.name}, Errors: ${errors}`,
);
}
return transformed;
});
}
plainToClass 处理数组,但是我们需要一些通用的东西来清楚地返回数组或基于您正在解析的内容的单个对象,因此不可能有一个带有返回签名的单个方法,例如 T |T[].
0赞
Simone Gianni
9/27/2023
#2
不幸的是,你不能。与许多语言(例如 Java)一样,泛型在编译代码时被删除。在 Typescript 中,更糟糕的是,“类型”被删除,这意味着它会转换为弱类型的 javascript。
因此,一旦在运行时,以下代码:
parseJsonField<T>(jsonValue: Prisma.JsonValue): T {
return JSON.parse(JSON.stringify(jsonValue));
}
减少到:
parseJsonField(jsonValue) {
return JSON.parse(JSON.stringify(jsonValue));
}
因此,没有要检查的类型信息。
此外,您正在使用一个类,请注意,即使您能够检查返回的值是否符合 的接口,它也不会是 的实例,而只是一个恰好具有与 的实例同名的泛型对象。T
T
T
您必须在运行时实现检查。与上面发布的解决方案类似,您可以概括它:
// Gets a value and deletes it from the json object
function getDelete<T>(jsonValue :any, property :string, required = false) :T {
const ret = jsonValue[property];
delete jsonValue[property];
if (required && typeof ret === 'undefined') {
throw new Error(`Property ${property} not found`);
}
return ret;
}
// Checks that the json object is empty, otherwise there are extraneous values
function checkEmpty(jsonValue: any) {
if (Object.keys(jsonValue).length > 0) {
throw new Error('Object contains extraneous properties ' + JSON.stringify(jsonValue));
}
}
// Example of a class
public class Person {
private firstName :string;
private lastName :string;
constructor(jsonValue? :any) {
if (jsonValue) {
// This is where we "parse" the json object into the actual class, and check it
this.firstName = getDelete(jsonValue, 'firstName');
this.lastName = getDelete(jsonValue, 'lastName');
}
checkEmpty(jsonValue);
}
}
function parseAndCheck<T>(jsonValue :any, constructor: (...arg: any[])=>T) :T {
return constructor(JSON.parse(JSON.stringify(jsonValue)));
}
(上面的代码没有测试过,只是为了给你一个想法)
评论