user2115620
user2115620

Reputation: 111

How to access object properties of a custom interface using a string in typescript

I need to access the properties of my object dynamically using a given string as the key.

I have checked the following, but I can't find the answer I am looking for:

TypeScript dynamic object properties

Typescript dynamic access property

The error happens here:

state.value[container][id] // TS2571: Object is of type 'unknown'

When calling method 'getEntity' should I be passing an ENUM for the property instead of a string?

Below is a simplified example of my code - errors are on lines 57 and 69:

// interfaces for entity types - including these in this example seems unnecessary
import {
  TaskFinances,
  Plan,
  Boundary,
  BoundaryMapList
} from 'DTOs'

// for pulling data from REST
import axios, { AxiosResponse } from 'axios'

// VueJS composition AIP components
import { ref } from '@vue/composition-api'

// I want to imply that the State interface below is made up of string:unknown pairs
// so that I can reference it using a string key in the <getEntity> method below

interface Stately {
  [key: string]: unknown
}
interface State extends Stately {
  plans: { [key: number]: Plan };
  taskFinances: { [key: number]: TaskFinances };
  boundaryPointsByField: Boundary[];
}

const props: State = {
  plans: {},
  taskFinances: {},
  boundaryPointsByField: []
}

const state = ref(props)
// I reference the properties of state using state.value.xxx because it is using the Vue3 Composition API Ref syntax

// Reduced to 2 lines following D.R.Y. principles
function getTaskFinances(activityId: number): Promise<TaskFinances> {
  const taskFinances = getEntity(activityId, 'taskFinance', 'taskFinances') as unknown as Promise<TaskFinances>
  return taskFinances
}

// Reduced to 2 lines following D.R.Y. principles
function getBoundaryPointsByField(fieldId: number): Promise<BoundaryMapList> {
  const boundaryPoints = getEntity(fieldId, 'boundaryPointsByField', 'boundaryPointsByField') as unknown as Promise<BoundaryMapList>
  return boundaryPoints
}

// Reduced to 2 lines following D.R.Y. principles
function getPlan(planId: number): Promise<Plan> {
  const plan = getEntity(planId, 'plan', 'plans') as unknown as Promise<Plan>
  return plan
}

// generic method to return requested entities and add them to my state object
async function getEntity(id: number, api: string, container: string): Promise<unknown> {
  try {
    const entity: unknown = state.value[container][id] // THROWS typescript error -TS2571: Object is of type 'unknown'-
    if (entity) {
      return entity
    }
  } catch (err) {
    console.log(container, id, err)
  }

  const url = `${api}${id}`
  try {
    const { data }: AxiosResponse<unknown> = await axios.get(url)
    console.log(api + ':', data)
    state.value[container][id] = data  // THROWS typescript error -TS2571: Object is of type 'unknown'-
    return data
  } catch (err) {
    console.error(err)
  }
}

// interface for my exported uiState object - I use this interface elsewhere in the app so I am exporting it
export interface IUIState {
  getBoundaryPointsByField: (fieldId: number) => Promise<BoundaryMapList>;
  getTaskFinances: (activityId: number) => Promise<TaskFinances>;
  getPlan: (planId: number) => Promise<Plan>;
}

// export my uiState object
export const uiState: IUIState = {
  getBoundaryPointsByField,
  getTaskFinances,
  getPlan
}

Upvotes: 0

Views: 2206

Answers (1)

discolor
discolor

Reputation: 1386

This behaviour is intended.

Where you access your state here

 const entity: unknown = state.value[container][id]

state.value[container] is now of type unknown. You cannot try and access [id] behind it because TS has no clue if that even exists on state.value[container]. It's unknown. That is the downfall of using unknown. If you know your API responses, create interfaces for them and declare those as possible types for your state. Otherwise, if you want to force it to work without types, you'd have to use any instead of unknown.

You can see this behaviour reproduced here. You can see how the variable b is of type unknown and throws an error already. If you change the type to any in the interface, it will work but you won't get any type-safety.

Upvotes: 1

Related Questions