You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular RxJS TypeScript 转换字典添加validator属性方案

问题背景

现有返回类型为 { [key: string]: QueryBuilderFieldsDto } 的字典对象,原始结构如下:

{
    "2221": {
        "label": "Risk Domain",
        "name": "2221",
        "type": "dropdown",
        "typeLabel": "Dropdown",
        "isStandardField": false,
        "fieldEntityType": 2,
        "fieldEntityTypeLabel": "Risk",
        "options": [{
            "uniqueId": null,
            "id": 2636,
            "name": "Unassigned",
            "isOrganizationUnit": false
        }]
    },
    "2222": {
        "label": "Number Field",
        "name": "2222",
        "type": "number",
        "typeLabel": "Number",
        "isStandardField": false,
        "fieldEntityType": 2,
        "fieldEntityTypeLabel": "Risk"
    }
}

QueryBuilderFieldsDto 接口的属性定义如下:

export interface QueryBuilderFieldsDto {
    name: string;
    label: string;
    value?: string;
    type: string;
    nullable?: boolean;
    options?: Option[] | FieldOptionsDto[];
    operators?: string[];
    defaultValue?: any;
    defaultOperator?: any;
    isStandardField: boolean;
    fieldEntityType: number;
    fieldEntityTypeLabel?: string;
    typeLabel?: string;
    validator?: (rule: Rule, parent: RuleSet) => any | null;
}
实现目标

为字典对象的每个字段项追加自定义validator属性,校验逻辑如下:

validator: (rule) => {
  if (rule.value == undefined || rule.value == "") {
    return {
      age: {
        rule: rule,
        message: 'Value cannot be empty'
      }
    }
  }
}

最终效果参考(以2222字段为例):

"2222": {
    "label": "Number Field",
    "name": "2222",
    "type": "number",
    "typeLabel": "Number",
    "isStandardField": false,
    "fieldEntityType": 2,
    "fieldEntityTypeLabel": "Risk",
    "validator": (rule) => {
        if (rule.value == undefined || rule.value == "") {
            return {
                age: {
                    rule: rule,
                    message: 'Value cannot be empty'
                }
            }
        }
    }
}
现存问题

当前通过如下代码订阅Observable获取数据:

this.metricCreateConfigureStore.queryBuilderFields$.subscribe(item => {
  this.fields = item as { [key: string]: QueryBuilderFieldsDto };
})

其中queryBuilderFields$的类型为 queryBuilderFields$: Observable<{ [key: string]: QueryBuilderFieldsDto; }>,订阅拿到的返回值默认不包含validator属性。已知可通过RxJS的pipe搭配map操作符完成对象转换,要求map操作的返回值类型仍为{ [key: string]: QueryBuilderFieldsDto },但自行编写转换代码时VSCode会提示类型错误,需要可直接运行的正确实现方案。

正确实现代码

类型报错的核心原因是遍历对象追加属性时,TS没有正确识别新增属性的类型匹配关系,同时直接修改源状态对象也会带来隐性风险,正确代码如下:

import { map } from 'rxjs/operators';

// 抽离通用校验函数,避免重复创建函数实例
const nonEmptyValidator = (rule: Rule) => {
  if (rule.value === undefined || rule.value === "") {
    return {
      age: {
        rule: rule,
        message: 'Value cannot be empty'
      }
    }
  }
  // 校验通过返回null,符合接口类型定义
  return null;
};

this.metricCreateConfigureStore.queryBuilderFields$
  .pipe(
    // 显式声明map返回值类型,让TS做结构校验
    map((originFields): { [key: string]: QueryBuilderFieldsDto } => {
      const result: { [key: string]: QueryBuilderFieldsDto } = {};
      // 遍历所有字段,生成带validator的新字段对象
      for (const fieldKey of Object.keys(originFields)) {
        result[fieldKey] = {
          ...originFields[fieldKey],
          validator: nonEmptyValidator
        };
      }
      return result;
    })
  )
  .subscribe(fields => {
    this.fields = fields;
  })

注意事项

  • 不要直接修改源Observable返回的对象:如果queryBuilderFields$取自全局状态store,直接修改原对象属性会破坏状态不可变原则,导致状态更新异常,因此代码中通过新建对象、展开原有属性的方式生成新字段副本
  • 给map操作符显式标注返回值类型,TS会自动校验返回结构是否符合接口要求,避免隐式类型推导带来的类型不匹配报错
  • 校验逻辑最后补充return null,符合接口中validator返回any | null的定义;原逻辑校验通过时无返回值会隐式返回undefined,部分查询构建器组件会将非null返回值判定为校验失败,补充返回null可避免校验逻辑异常
  • 校验函数单独抽离定义,避免每次数据流触发都创建新的函数实例,减少不必要的内存开销

内容的提问来源于stack exchange,提问作者San Jaisy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 00:18:15