Search code examples
javascriptnulltail-recursiontypeof

null being assigned as object in tail recursive function


I have a tail recursive function whose purpose is to find any number in any number of nested objects and simply run the toFixed() on it.

The function formatData() works by looping through the object, testing to see what type of value is the current iteration is on, performing a function(toFixed()) on said iteration and saving it in a new object or do nothing and just saving the value as is. The end result will be a an identical object but with any long decimal place numbers trimmed to two decimal places.

During testing I've found that if there is a null value in the object at any level, it is first saved as null but later ends up being an object. I'm aware that in JS null is considered of type object and the typeof keyword will show this.

Here is a codepen of the problem: https://codepen.io/Danny_Wobble/pen/YzqMzxw Please note that the third console entry shows the value being correctly assigned/saved as null. Later on as described it ends up as an object.

Function:

function formatData(data) {
    const formattedData = {}
    for (const key in data) {
        if (data[key] === null) formattedData[key] = null
        // if (data[key] === null) formattedData[key] = 'null' // have tried saving as diff val, didn't work
        if (data[key] === false) formattedData[key] = false
        if (typeof data[key] === 'number') formattedData[key] = data[key].toFixed(2)
        if (typeof data[key] === 'object') formattedData[key] = formatData(data[key]) // tail recursion
    }
    return formattedData
}

Given:

const data = {
     status: false,
     survey: {
         2018: 3.4288,
         2019: 3.47701,
         2020: null,
     },
     benchmarks: {
         company: 3.455,
         industry: 3.5,
         portfolio: 3.4,
     },
}
const formattedData = formatData(data)

Expectation (formattedData):

{
     "status": false,
     "survey": {
         "2018": "3.43",
         "2019": "3.48",
         "2020": null,
     },
     "benchmarks": {
         "company": "3.50",
         "industry": "3.50",
         "portfolio": "3.40",
     },
}

Actual result (formattedData):

{
  "status": false,
  "survey": {
    "2018": "3.43",
    "2019": "3.48",
    "2020": {} // this should be `null`
  },
  "benchmarks": {
    "company": "3.50",
    "industry": "3.50",
    "portfolio": "3.40"
  }
}

Solution

  • You need to exclude null values from the object check.

    if (data[key] && typeof data[key] === 'object')
    //  ^^^^^^^^^^^^
    

    You could change the logic for checking only the values for different processing, like numbers and objects and continue or just assign the given value to the new object.

    function formatData(data) {
        const formattedData = {}
        for (const key in data) {
            if (typeof data[key] === 'number') {
                formattedData[key] = data[key].toFixed(2)
                continue;
            }
            if (data[key] && typeof data[key] === 'object') {
                formattedData[key] = formatData(data[key]);
                continue;
            }
            formattedData[key] = data[key];
        }
        return formattedData;
    }
    
    const
        data = {
            status: false,
            survey: {
                2018: 3.4288,
                2019: 3.47701,
                2020: null
            },
            benchmarks: {
                company: 3.455,
                industry: 3.5,
                portfolio: 3.4
            }
        },
        formattedData = formatData(data);
    
    console.log(formattedData);