2

I'm trying to retrieve the keys of a nested object dynamically. An example of the object I can have :

{
    ts: "2021-05-06T11:06:18Z",
    pr: 94665,
    pm25: 5,
    co2: 1605,
    hm: 32,
    m: {
      isConnected: true,
      wifidBm: 0,
    },
    pm1: 5,
    s: {
      "0": {
        runningTime: 0,
        sn: "05064906137109790000",
        wearRate: 0,
        enabled: true,
        co2: 1605,
      },
      "1": {
        enabled: false,
        sn: "125630092251006",
      },
      "2": {
        enabled: false,
        sn: "05064906137109450000",
      },
      "3": {
        fanStatus: 0,
        pm25: 5,
        pm1: "5",
        e: {
          rawSensorError: 0,
        },
        pm10: 5,
        runningTime: 0,
        sn: "125630100924141",
        wearRate: 0,
        enabled: true,
      },
    },
    id: "avo_jggv6bsf211",
    tp: "20.6",
    pm10: 5,
    type: "monitor",
  }

For exemple, I'll need to have :

str = 'ts, pr, pm25, co2, hm, "m.isConnected, m. wifiBm, pm1, s.0.runningTime, s.0.sn, ...' 

and that's my code for now :

guessHeader(object: MeasurementModel | any, parentKey?: string): string[] {
    // Looping on object's keys
    Object.keys(object).forEach(key => {
      if (typeof object[key] === 'object' && key !== 'ts') {
        // If the object is an array recurse in it
        return this.guessHeader(object[key], key)
      } else {
        // If we have a parentKey keep header as
        if (parentKey)
          this.header.push(`${parentKey}.${key}`)
        else
          this.header.push(key)
      }
    })
    return this.header
  }

It's working for the key m, I have m.isConnected and m.wifiBm but for s.0.runningTime I just have 0.runningTime. Also, this object can change and be even more nested. I need to find a way that will works for any cases. I tried to save the keys in an array and then parse them but failed.

Le Rilleur
  • 205
  • 2
  • 15

1 Answers1

0

The issue is here:

// If the object is an array recurse in it
return this.guessHeader(object[key], key)

This should be

this.header.push(...this.guessHeader(object[key], parentKey === undefined ? key : `${parentKey}.${key}`))

Also, typeof null === 'object', so you should also check if that object[key] is non-null (e.g. typeof object[key] === 'object' && object[key]) before passing it to guessHeader.

Another thing is that MeasurementModel | any is the same as any. Avoid using any and use unknown instead, which is type-safe.

An implementation of guessHeader could look like this:

guessHeader(object: object, parentKey?: string): string[] {
  Object.keys(object).forEach(key => {
    const newKey = parentKey === undefined ? key : `${parentKey}.${key}`
    const value = (object as Record<string, unknown>)[key]
    if (typeof value === 'object' && value && key !== 'ts')
      this.header.push(...this.guessHeader(value, newKey))
    else
      this.header.push(newKey)
  })
  return this.header
}

Personally, I would use Object.entries and do something like this:

const guessHeader = (object: object, acc = ''): string =>
  Object.entries(object)
    .map(([key, value]: [string, unknown]) =>
      // It looks like you don't want to recurse into the ts key,
      // which is why I used key !== 'ts'
      // I'm guessing ts might be a Date; you could avoid recursing into
      // all Dates by doing
      // typeof value == 'object' && value && !(value instanceof Date)
      key !== 'ts' && typeof value === 'object' && value
        ? guessHeader(value, `${acc}${key}.`)
        : acc + key
    )
    .join(', ')

const input = {ts: '2021-05-06T11:06:18Z', pr: 94665, pm25: 5, co2: 1605, hm: 32, m: {isConnected: true, wifidBm: 0}, pm1: 5, s: {0: {runningTime: 0, sn: '05064906137109790000', wearRate: 0, enabled: true, co2: 1605}, 1: {enabled: false, sn: '125630092251006'}, 2: {enabled: false, sn: '05064906137109450000'}, 3: {fanStatus: 0, pm25: 5, pm1: '5', e: {rawSensorError: 0}, pm10: 5, runningTime: 0, sn: '125630100924141', wearRate: 0, enabled: true}}, id: 'avo_jggv6bsf211', tp: '20.6', pm10: 5, type: 'monitor'}

// same as above code block
const guessHeader = (object, acc = '') => Object.entries(object).map(([key, value]) => key !== 'ts' && typeof value === 'object' && value ? guessHeader(value, `${acc}${key}.`) : acc + key).join(', ')

console.log(guessHeader(input))
Lauren Yim
  • 12,700
  • 2
  • 32
  • 59