How to properly index and modify generic partial

Viewed 404

This is my first generic type function and will appreciate if somebody could assist with my missing understanding of Partials and in general TS behaviour.

What is intended to achieve:
1. Pass object of type <T>
2. Modify it and return expected object of type <U>

The example below with the live warnings in codesandbox: https://codesandbox.io/s/small-wood-yq14e

P.S: I am open to new ideas of how the whole thing should be actually approached in TS best practices :-)

export type ObjectWithStrings = { [index: string]: string }


// some demo data
const CONTAINER_NUTRIENTS_NAMES_ORDER: ObjectWithStrings = {}
const LIPIDS_NUTRIENTS_NAMES_ORDER: ObjectWithStrings = {}
const NUTRIENTS_MODEL_MAP: ObjectWithStrings = {}

function _sortNutrientsModel<T> (nutrientsModel: T) {
  const sortedNutrientsModel: Partial<T> = {}

  for (const sectionName in CONTAINER_NUTRIENTS_NAMES_ORDER) {
    if (CONTAINER_NUTRIENTS_NAMES_ORDER.hasOwnProperty(sectionName)) {
      /* 
      sortedNutrientsModel[sectionName]:
      Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Partial<T>'.
      No index signature with a parameter of type 'string' was found on type 'Partial<T>'.ts(7053)

      nutrientsModel[sectionName]:
      Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'unknown'.
      No index signature with a parameter of type 'string' was found on type 'unknown'.ts(7053) */
      sortedNutrientsModel[sectionName] = nutrientsModel[sectionName]
    }
  }

  for (const sectionName in LIPIDS_NUTRIENTS_NAMES_ORDER) {
    /* 
    Property 'lipids' does not exist on type 'T'.ts(2339) */
    if (LIPIDS_NUTRIENTS_NAMES_ORDER.hasOwnProperty(sectionName) && sortedNutrientsModel.lipids.sections[sectionName]) {
      sortedNutrientsModel.lipids.sections[sectionName] = nutrientsModel.lipids.sections[sectionName]
    }
  }

  return sortedNutrientsModel
}

export function createNutrientsModel<T extends object, U> (nutrients: T) {
  const nutrientsModel: Partial<U> = {}

  for (const nutrientName in nutrients) {
    if (nutrients.hasOwnProperty(nutrientName)) {
      const nutrientLocation = NUTRIENTS_MODEL_MAP[nutrientName]

      if (typeof nutrientLocation === `string`) {
        const nutrientValues = nutrients[nutrientName]

        switch (nutrientLocation.length) {
          case 0: /* Container nutrient section */
            /*
            Type 'Extract<keyof T, string>' cannot be used to index type 'Partial<U>'.ts(2536) */
            if (typeof nutrientsModel[nutrientName] === `undefined`) {
             /*
              Type 'Extract<keyof T, string>' cannot be used to index type 'Partial<U>'.ts(2536) */
              nutrientsModel[nutrientName] = {}
            }

           /*
            Type 'Extract<keyof T, string>' cannot be used to index type 'Partial<U>'.ts(2536) */
            nutrientsModel[nutrientName].values = nutrientValues
            break
          default: /* Nutrient section */
           /*
            Type 'Extract<keyof T, string>' cannot be used to index type 'Partial<U>'.ts(2536) */
            if (nutrientsModel[nutrientLocation] === `undefined`) {
             /*
              Type 'Extract<keyof T, string>' cannot be used to index type 'Partial<U>'.ts(2536) */
              nutrientsModel[nutrientLocation] = {}
            }

           /*
            Type 'Extract<keyof T, string>' cannot be used to index type 'Partial<U>'.ts(2536) */   
            if (typeof nutrientsModel[nutrientLocation].sections === `undefined`) {
              nutrientsModel[nutrientLocation].sections = {}
            }

           /*
            Type 'Extract<keyof T, string>' cannot be used to index type 'Partial<U>'.ts(2536) */ 
            nutrientsModel[nutrientLocation].sections[nutrientName] = {
              values: nutrientValues
            }
        }
      }
    }
  }

  return _sortNutrientsModel(nutrientsModel) as U
}

UPDATE with example of NutrientsType

interface BalanceModelSection {
  DRI: {
    AI: number,
    AMDR: BalanceModelAMDR
    EAR: number,
    RDA: number,
    UL: number,
    unit: string
    [index: string]: string | number | BalanceModelAMDR
  }
}

interface BalanceRecordSectionValues {
  converted: {
    quantity: number
    recommended: number
    unit: string
  }
  quantity: number
  percentage: string
}

interface BalanceRecordSection extends
  BalanceRecordSectionValues,
  BalanceModelSection {}

interface BalanceRecordNutrients {
  [index: string]: BalanceRecordSection | BalanceRecordProgressSection
  energy: BalanceRecordSection
}
0 Answers
Related