1

I have this object model:

export class FrcCapacity {
  constructor(
    public id?: number,
    public frcId?: number,
    public capGroupId?: number,
    public capGroup?: CapGroup,
    public salesProductId?: number,
    public p1?: number,
    public p2?: number,
    public p3?: number,
    public p4?: number,
    public p5?: number,
    public p6?: number,
    public p7?: number,
    public p8?: number,
    public p9?: number,
    public p10?: number,
    public p11?: number,
    public p12?: number,
    public total?: number
  ) {}
}

And I have an array of this, f. e. frcCapacity, filled with objects of the above object model.

I want to write a function, where I want to set the px value of the processed object. For this, I have all needed data and the function body looks like this:

periodValueChange(val: string, rowIndex: number, field: string) {
    for (let [key, value] of Object.entries(this.frcCapacity[rowIndex])) {
      if (key === field) this.frcCapacity[rowIndex]???
    }
  }

I'm trying this with Object.entries, but what should I write in place of the ???. How can I access the px field based on the field string parameter?

After some thinking and searching, this solution works:

periodValueChange(val: string, rowIndex: number, field: string) {
    let frcCap = this.frcCapacity[rowIndex];
    let map = new Map(Object.entries(frcCap));
    for (let [key, value] of map) {
      if (key === field) {
        map.set(field, +val);
      }
    }
    let obj = Array.from(map).reduce(
      (obj, [key, value]) => Object.assign(obj, { [key]: value }),
      {}
    );
    this.frcCapacity[rowIndex] = obj;
  }

Basically, I needed something like this:

periodValueChange(val: string, rowIndex: number, field: string) {
    this.frcCapacity[rowIndex].field = +val;
  }

Where the field parameter can be p1, p2, etc.

derstauner
  • 1,478
  • 2
  • 23
  • 44
  • @Andreas - This is TypeScript, not JavaScript, which is relevant here (not always, but in this case). Still, there must be a TypeScript version of that... – T.J. Crowder Jan 27 '22 at 08:25
  • You have `val` as `string`, but **none** of `FrcCapacity`'s properties have the type `string`. Most of them are `number`, and one of them is `CapGroup`. So why does this take string? – T.J. Crowder Jan 27 '22 at 08:29
  • [How to dynamically access object property in TypeScript](https://stackoverflow.com/questions/41993515/access-object-key-using-variable-in-typescript) – Andreas Jan 27 '22 at 08:31
  • @Andreas Still doesn't handle the issue of the property type, that's just keys. There **must** be one (I even think I've seen it, related to setting object properties based on form fields), but I'm not having any luck finding it. – T.J. Crowder Jan 27 '22 at 08:33
  • The solution you've posted is much, much more complex than needed, and has the problem that the resulting object doesn't inherit from `FrcCapacity.prototype`. The reason it works is the loose typing around `Object.entries` and the untyped map. You might as well just do `(this.frcCapacity[rowIndex] as any)[field] = +val;` if you're not worried about type safety. – T.J. Crowder Jan 27 '22 at 09:58
  • Ok, I see now. Indeed, my solution is complex and maybe a bit hacky. I'm completly fine with `(this.frcCapacity[rowIndex] as any)[field] = +val;` too. – derstauner Jan 27 '22 at 10:08

1 Answers1

1

Since you've used string for both the property name (field) and the value (val), I'm going to assume that you're getting those strings from somewhere that you can't get non-strings from (like form fields). So there are two challenges:

  1. field could be an invalid property name for FrcCapacity objects, and

  2. val may not be a valid value for whatever property field identifies.

To handle this in a mostly-typesafe way, you'll need a function that validates that a key is a valid FrcCapacity key, such as a type assertion function:

function assertIsValidFrcCapacityKey(key: string): asserts key is keyof FrcCapacity {
    switch (key) {
        case "id":
        case "frcId":
        case "capGroupId":
        // ...and so on for all the valid property names...
            break;
        default:
            throw new Error(`${key} is not a valid key for FrcCapacity objects`);
    }
}

That tells TypeScript that if that function doesn't throw, the key passed in is a valid key for FrcCapacity objects. Obviously, it's less than ideal that you have to repeat all the property names, but there we are.

Then you have to handle the number / CapGroup thing, which you can hardcode into the function:

periodValueChange(val: string, rowIndex: number, field: string) {
    assertIsValidFrcCapacityKey(field);
    if (field === "capGroup") {
        frcCapacity[rowIndex].capGroup = convertStringToCapGroup(val);
    } else {
        frcCapacity[rowIndex][field] = +val;
    }
}

(In that example I've used unary + to convert the val string to a number, but there are other ways; I list the various options and their pros and cons here. I've also left the implementation of convertStringToCapGroup to you, since I don't know what a CapGroup is, nor how you're encoding it as a string in your form.)

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Why there is a community wiki ? What does it mean? – captain-yossarian from Ukraine Jan 27 '22 at 08:58
  • I feel certain there's a good dupetarget for this, but couldn't find one, so ultimately just ended up posting an answer. If anyone does find one, please @ ping me so I can remove it (and add that question to my list of good dupetargets for TypeScript). – T.J. Crowder Jan 27 '22 at 08:58
  • 1
    @captain-yossarian - LOL, overlapping comments -- see my comment just above. I mark answers Community Wiki when I want to avoid any impression that I'm answering just to get rep (which sadly many people who post answers to duplicates do). CW answers don't earn rep. I can't imagine this isn't a duplicate question, but I can't find a good target to point to. So doing this is my compromise. :-) – T.J. Crowder Jan 27 '22 at 08:59
  • thank you for an explanation. I have never used this feature before. – captain-yossarian from Ukraine Jan 27 '22 at 09:01
  • @T.J.Crowder, thank you for your time to answer this. I'm aware, that `val` is string and that I have to convert it. Maybe I wasn't clear enough in the question, that I only want to set the `p1`, `p2`, etc. properties based on the passed `field` argument. I also already tried `frcCapacity[rowIndex][field] = +val;`, but this has the error, that string cannot applied as index. Basically, the usual error. But in the meantime, I have a working solution, see the edited question. – derstauner Jan 27 '22 at 09:48
  • @derstauner - The way you solve that error is with a type assertion function like the one I showed (you might limit it to the `p1`, `p2`, etc. fields). – T.J. Crowder Jan 27 '22 at 09:50
  • I will give it a try. – derstauner Jan 27 '22 at 09:53