Home > Software engineering >  How to update object data based on string input in typescript?
How to update object data based on string input in typescript?

Time:04-15

I want to update an object's value based on the input of a string that's related to an object key, how should I go about this with typescript?

const objData = { // random value
  A: 11,
  B: 13,
  C: 53,
  innerObj: {
    AStatus: true,
    BStatus: false,
    CStatus: true,
  },
};

type Item = 'itemA' | 'itemB' | 'itemC';

function processObj(item: Item, obj: typeof objData) {
  if (item === 'itemA') { // <- duplicate
    obj.A = 5;
    obj.innerObj.AStatus = true;
    return obj;
  }

  if (item === 'itemB') { // <- duplicate
    obj.B = 5;
    obj.innerObj.BStatus = true;
    return obj;
  }

  if (item === 'itemC') { // <- duplicate
    obj.C = 5;
    obj.innerObj.CStatus = true;
    return obj;
  }
  return
}

processObj('itemA', objData);

For instance, the input is itemA, and only A related objData gets updated.

Typescript playground

CodePudding user response:

If the relationship between the item parameter and the key names of obj and obj.innerObj is programmatically determined by string manipulation, such that there is a property key k of obj corresponding to a property k "Status" of obj.innerObj and to the value "item" k of item, then you can refactor your processObj function to use template literal types. Template literal types allow you to represent some string literal manipulations at the type level. Here's one way to do it:

type Keys = Exclude<keyof typeof objData, "innerObj">;
// type Keys = "A" | "B" | "C"

function processObj(item: `item${Keys}`, obj: typeof objData) {
  const k = item.substring(4) as Keys; // need to assert here 
  obj[k] = 5;
  obj.innerObj[`${k}Status`] = true;
  return obj;
}

The Keys type uses the Exclude<T, U> utility type to filter the keys of objData to remove "innerObj", leaving us with the union "A" | "B" | "C".

For the rest of it, we're using template literal types to perform string concatenation at the type level. The type of item is `item${Keys}`, which evaluates to "itemA" | "itemB" | "itemC". We can calculate k from item by stripping the initial "item" prefix from it; the result is of type Keys, but the compiler can't verify that. Thus we just assert that k is of type Keys.

We can just set obj[k] = 5 with no compiler warning because the compiler understands that obj has a number property at all keys in Keys. And we can also set obj.innerObj[`${k}Status`] = true with no compiler warning, because the compiler understands that a template literal string value can have a template literal type, and that the type of `${k}Status` is `${Keys}Status`, which evaluates to "AStatus" | "BStatus" | "CStatus". And the compiler knows that obj.innerObj has a boolean property at those keys.

So this all works as desired.


On the other hand, if the relationship between item and the keys of obj and obj.innerObj is arbitrary, then you can't necessarily use string manipulation to map between them. In this case, you would want something like a lookup table to represent the mapping, whatever it is. Such an implementation might look like this:

const propLookup = {
  itemA: "A",
  itemB: "B",
  itemC: "C"
} as const;
const statusLookup = {
  itemA: "AStatus",
  itemB: "BStatus",
  itemC: "CStatus"
} as const;
type Keys = keyof typeof propLookup;
// type Keys = "A" | "B" | "C"

function processObj(item: Keys, obj: typeof objData) {
  obj[propLookup[item]] = 5;
  obj.innerObj[statusLookup[item]] = true;
  return obj;
}

The propLookup and statusLookup objects are just maps from valid item values to the corresponding properties of obj and obj.innerObj. They are using const assertions so the compiler keeps track of the string literal types of the values. If we left off as const then the compiler would infer just string for the values, which doesn't help us.

This also works as desired; the compiler understands that propLookup[item] is a key of obj with a number value, and that statusLookup[item] is a key of obj.innerObj with a boolean value.

Playground link to code

  • Related