Home > OS >  How to add a property to a dynamically changing object from an API?
How to add a property to a dynamically changing object from an API?

Time:10-24

I hope someone can help me with my problem! I didn't find the right thing through the search and maybe someone can give me a hint.

I am calling an API that returns an object that in turn contains nested objects. In these nested objects there are two properties "value" and "scale". I want to divide these two properties and write them as properties in the same object.

The data I get from the API is dynamic, which means it is constantly changing.

Example:

// call api

const apiCall = callApi(...);

// return object
console.log(apiCall);

{
    id: '3454353458764389759834534534',
    json_data: {
        persons: {
            de: {
                name: 'Bob',
                data: {
                    scale: 100,
                    value: 2459,
                },
            },
            be: {
                name: 'Alice',
                data: {
                    scale: 10000,
                    value: 1459,
                },
            },
        },
        url: 'https://stackoverflow.com/',
        timestamp: '2021-10-23T12:00:11 00:00',
        disclaimer: 'Some bla bla',
    },
}

// targed object


const objTarged = {
    id: '3454353458764389759834534534',
    json_data: {
        persons: {
            de: {
                name: 'Bob',
                data: {
                    scale: 100,
                    value: 2459,
                    result: 24.59 // value / scale = result
                },
            },
            be: {
                name: 'Alice',
                data: {
                    scale: 10000,
                    value: 1459,
                    result: 0.1459 // value / scale = result
                },
            },
        },
        url: 'https://stackoverflow.com/',
        timestamp: '2021-10-23T12:00:11 00:00',
        disclaimer: 'Some bla bla',
    },
};

My thoughts:

  • do I need to map the object into a new object?
  • how can I do this if the source object is constantly changing (Object.values?)
  • How can I write the result of Value / Scale as a new property in the same object each time I call the API?

Big thanks in advance :)

CodePudding user response:

I would create a mapValues() function that takes an object, and creates a new object by passing each of the object's values in a transforming function.

Whenever the api call returns a new object, we recreate the new object with the result property according to the structure.

How does the mapValues function works?

Whenever an object (or array) is passed to mapValues, it's converted to an array of [key, value] pairs. The pairs are then mapped to new [key, pair] entries by applying transformFn to the value. The transform array of pairs is then converted back to an using Object.fromEntries().

const mapValues = (transformFn, obj) => Object.fromEntries(
  Object.entries(obj)
  .map(([key, value]) => [key, transformFn(value)])
)

const apiCall = {"persons":{"de":{"name":"Bob","scale":100,"value":2459},"be":{"name":"Alice","scale":10000,"value":1459}}}

const result = mapValues(
  val => mapValues(v => ({
    ...v,
    result: v.value / v.scale,
  }), val),
  apiCall
)

console.log(result)
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

If you have multiple nested levels with properties you don't want to transform, we can also pass the key to the transformFn for a more granular change. Now we can create a recursive function to traverse the tree, and only update objects which have a specific key.

const mapValues = (transformFn, obj) => Object.fromEntries(
  Object.entries(obj)
  .map(([key, value]) => [key, transformFn(value, key)])
)

const fn = obj => mapValues(
  (val, key) => {
    // if the key is data create a new object with a result property
    if(key === 'data') return ({
      ...val,
      result: val.value / val.scale,
    })
    
    // if it's object pass it to the recursive function
    if(typeof val === 'object') return fn(val)

    return val
  },
  obj
)

const apiCall = {"id":"3454353458764389759834534534","json_data":{"persons":{"de":{"name":"Bob","data":{"scale":100,"value":2459}},"be":{"name":"Alice","data":{"scale":10000,"value":1459}}},"url":"https://stackoverflow.com/","timestamp":"2021-10-23T12:00:11 00:00","disclaimer":"Some bla bla"}}

const result = fn(apiCall)

console.log(result)
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

CodePudding user response:

It might be helpful to decompose the problem into first finding the nested objects with the keys you're interested in. Having done that, it will be easy to augment those objects with the desired calculation.

Below is a sort of generic function that finds a nested object based on it having a particular key. With that, fixMyApiData writes itself...

// return an array of objects that are nested in the passed object which contain the passed key
function objectsContainingKey(object, key) {
  let results = [];
  Object.keys(object).forEach(k => {
    if (k === key) results.push(object);
    if (object[k] && typeof object[k] === 'object')
      results = results.concat(objectsContainingKey(object[k], key));
  });
  return results;
}

// find the nested objects we care about and augment them with the value/scale calculation
function fixMyApiData(apiData) {
  objectsContainingKey(apiData, 'scale').forEach(data => {
    if (data.value) data.result = data.value / data.scale;
  })
}

let apiData = {
  id: '3454353458764389759834534534',
  json_data: {
    persons: {
      de: {
        name: 'Bob',
        data: {
          scale: 100,
          value: 2459,
        },
      },
      be: {
        name: 'Alice',
        data: {
          scale: 10000,
          value: 1459,
        },
      },
    },
    url: 'https://stackoverflow.com/',
    timestamp: '2021-10-23T12:00:11 00:00',
    disclaimer: 'Some bla bla',
  },
};

fixMyApiData(apiData);
console.log(apiData);
<iframe name="sif3" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

  • Related