sunknudsen
sunknudsen

Reputation: 7260

How to access nested JSON properties using TypeScript?

The following code block returns a TypeScript error.

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ id: string; path: string; basename: string; createdOn: string; modifiedOn: string; content: string; }'.

Square brackets work fine in JavaScript (or using //@ts-ignore).

What is the right way of doing this in TypeScript?

export interface Data {
  [key: string]: {
    id: string
    path: string
    basename: string
    createdOn: string
    modifiedOn: string
    content: string
  }
}

const data = _data as Data // _data is JSON

for (let item in data) {
  for (let property in data[item]) {
    console.log(property)
    if (["createdOn", "modifiedOn"].includes(property)) {
      data[item][property] = new Date(data[item][property])
    }
  }
}

Here’s what the JSON of _data looks like.

{
  "readme": {
    "id": "3905d7917f2b3429490b01cfb60d8f5b",
    "path": "README.md",
    "basename": "README.md",
    "createdOn": "2020-02-26T12:29:26.181Z",
    "modifiedOn": "2020-02-26T12:29:26.181Z",
    "content": "<!--\nTitle: Privacy guides\nDescription: Privacy guides will be published shortly...\n-->\n\n# Privacy guides\n\nPrivacy guides will be published shortly...\n"
  },
  ...
}

Upvotes: 1

Views: 1558

Answers (1)

Terry
Terry

Reputation: 66113

The reason is two fold:

  1. property has a string type, instead of being known as a key in the nested object
  2. data[item][property] = new Date(...) will return an error anyway, because your nested object only expect string as values

Since using .includes() or .indexOf() does not narrow the type properly, you will have to create a function that basically returns the correct keys. This serves as a type guard that will return the correct types from a union type:

function hasPossibleDateAsValue(value: string): value is 'createdOn' | 'modifiedOn' {
  return ["createdOn", "modifiedOn"].includes(value);
}

Then, you can do this without TypeScript throwing an error/warning:

for (let item in data) {
  for (let property in data[item]) {
    if (hasPossibleDateAsValue(property)) {
      data[item][property] = new Date(data[item][property]);
    }
  }
}

See proof-of-concept on TypeScript playround.

Upvotes: 2

Related Questions