在Typescript中是否可以检查泛型函数的返回对象类型的正确性?

问题描述 投票:0回答:2

我试图了解是否有一种方法可以验证不同可能类型的 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只是为了看日志)

json typescript validation prisma typechecking
2个回答
0
投票

经过几天的尝试,我和一位同事想出了这个解决方案,目前看来效果很好:

添加此导入:

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
投票

不幸的是你不能。与许多语言(例如 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)));
}

(尚未测试上面的代码,只是为了给您一个想法)

© www.soinside.com 2019 - 2024. All rights reserved.