Home > Blockchain >  How to dynamically parse object within a object where properties are a mix of types
How to dynamically parse object within a object where properties are a mix of types

Time:01-06

When trying to parse a object within a object as such:

let attribute: keyof Test

for (attribute in data) {
  if (typeof data[attribute] != 'string') {
    data[attribute]['numberProperty'] = 3
  }
}

With the following data:

var data: Test = {
  stringProperty: "someString",
  objectProperty: {
    numberProperty: 13
  }
}

And this interface:

interface Test {
  stringProperty: string
  objectProperty: {
    numberProperty: number
  }
}

I end up with the following error:

Element implicitly has an 'any' type because expression of type '"numberProperty"' can't be 
used to index type 'string | { numberProperty: number; }'.
  Property 'numberProperty' does not exist on type 'string | { numberProperty: number; }'.(7053)

Since the error complained about the string not being parsable as a object, I tried to remove it from the equation by logically emiting it and only allow objects to pass:

for (attribute in data) {
  if (typeof data[attribute] != 'string') { // This line is supposed to remove the string
    data[attribute]['numberProperty'] = 3
  }
}

However, this did not work. What can I do?

Here is a Typescript Playground if you want to see the error in action

CodePudding user response:

You were expecting the check of the expression data[attribute] to narrow its apparent type from the union string | { numberProperty: number } to { numberProperty: number }. But narrowing on property access is only supported when the key is of a single known literal type. Since attribute is itself a union type ("stringProperty" | "objectProperty"), no such narrowing takes place.

There is a longstanding issue at microsoft/TypeScript#10530 requesting support for narrowing all obj[key] expressions even if key is not a single literal type... such as string, or a union, or a generic. (The original issue was actually about all bracketed property access, but this part has since been fixed when the bracketed property is a single string literal, so obj["prop"] now narrows the same way obj.prop does. But the issue has stayed open to track the other cases.) It's not trivial to do this without degrading compiler performance, and so the feature remains missing.

Until and unless that changes, you need to work around it. The recommended workaround is, where possible, to copy the expression you want to narrow to its own variable, since variables can be narrowed as expected. In your example that would look like:

let attribute: keyof Test
for (attribute in data) {
  const val = data[attribute];
  if (typeof val !== 'string') {
    val['numberProperty'] = 3 // okay
  }
}

which compiles with no error because val is indeed narrowed by the check from string | { numberProperty: number } to { numberProperty: number }.

Playground link to code

  • Related