admin管理员组

文章数量:1398111

So I have this function...

    updateField ( tenantId: string, fieldname: string, value: string | number | boolean | Array<SubTenant> | undefined) {
      const tenantPartial = {} as Partial<Tenant>;
      if(fieldname != "id"){
              tenantPartial[fieldname as keyof Tenant] = value as any;
      }

      this.buildingService.updateTenantField(this.buildingId, tenantId, tenantPartial)
    }

that uses this interface (simplified to what matters for the question)

export interface Tenant{
  name: string;
  readonly id: string
}

and fails with this error :

Cannot assign to 'id' because it is a read-only property.

(parameter) fieldname: string

As you can see, I tried eliminating the possibility that 'id' is used as a keyof Tenant, but that doesn't clear the error. How do I clear this error?

So I have this function...

    updateField ( tenantId: string, fieldname: string, value: string | number | boolean | Array<SubTenant> | undefined) {
      const tenantPartial = {} as Partial<Tenant>;
      if(fieldname != "id"){
              tenantPartial[fieldname as keyof Tenant] = value as any;
      }

      this.buildingService.updateTenantField(this.buildingId, tenantId, tenantPartial)
    }

that uses this interface (simplified to what matters for the question)

export interface Tenant{
  name: string;
  readonly id: string
}

and fails with this error :

Cannot assign to 'id' because it is a read-only property.

(parameter) fieldname: string

As you can see, I tried eliminating the possibility that 'id' is used as a keyof Tenant, but that doesn't clear the error. How do I clear this error?

Share Improve this question asked Mar 25 at 20:56 Jon ThompsonJon Thompson 4464 silver badges19 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 1

The problem is that your fieldName is declared as a string, so != 'id' does not narrow it - Exclude<string, 'id'> is still string, TypeScript does not support negated types (see Type for "every possible string value except ...").

You can either change your parameter to keyof Tenant, which is basically 'id' | 'name', then that union will be narrows to just 'name' by your if statement:

updateField (fieldname: keyof Tenant, value) {
  const tenantPartial = {} as Partial<Tenant>;
  if (fieldname != "id") {
     tenantPartial[fieldname] = value;
  }
}

or if you want to keep the type assertion, you need express the elimination of 'id' yourself:

updateField (fieldname: string, value) {
  const tenantPartial = {} as Partial<Tenant>;
  if (fieldname != "id") {
     tenantPartial[fieldname as Exclude<keyof Tenant, 'id'>] = value;
  }
}

I think your whole approach of accepting any field name and value isn't in spirit of TS, actually you'd like to accept only writable key/value pair here most probably, in that case you should accept only writeable keys with corresponding values types. This will also give you a helpful intellisense:

Playground

interface Tenant{
  name: string;
  employeeCount: number,
  readonly id: string
}

type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false;

type GetReadonlyKeys<
  T,
  U extends Readonly<T> = Readonly<T>,
  K extends keyof T = keyof T
> = K extends keyof T ? Equal<Pick<T, K>, Pick<U, K>> extends true ? K : never : never

type GetWriteOnlyKeys<T> = Exclude<keyof T, GetReadonlyKeys<T>>;

function updateField<K extends GetWriteOnlyKeys<T>, T extends Tenant>(...[fieldname, value]: K extends any ? [K, T[K]] : never) {
  const tenantPartial = {} as Partial<T>;
  tenantPartial[fieldname] = value 
}

updateField("name", 'Tenant');
updateField("employeeCount", 1);
updateField("employeeCount", 'string'); // wrong value
updateField("id", 'string'); // not writable
updateField("name2", 'string'); // non existing prop

本文标签: