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

nkhmeac6  于 2023-10-21  发布在  TypeScript
关注(0)|答案(2)|浏览(121)

我试图了解是否有一种方法可以验证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只是用来查看日志)

q5iwbnjs

q5iwbnjs1#

经过几天的尝试,我和一位同事想出了这个解决方案,现在看来效果不错:
添加此导入:

import { ClassConstructor, plainToClass } from 'class-transformer';
import { validate } from 'class-validator';

我们为数组和单个值做了两个方法:

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[]。

anhgbhbe

anhgbhbe2#

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

(没有测试上面的代码,只是为了给你一个给予概念)

相关问题