Home > Software design >  Recursive function returns unexpected string
Recursive function returns unexpected string

Time:09-17

I'm trying to stringfy an object with my custom function, I just do it for practice purposes. But the function doesn't return what I want.

Here is the whole function and the object I pass into.

const data = { hello: "world", is: true, nested: { count: 5 } };

const stringify = (obj, symbol, numRepeat, stringifiedObj) => {
  const keys = Object.keys(obj);

  const values = Object.values(obj);
  // console.log("stringified object when functino runs", stringifiedObj);

  // console.log("ALL KEYS", keys);
  // console.log("ALL VALUES", values);

  keys.forEach((key, index) => {
    // console.log(typeof obj[key]);

    if (typeof values[index] !== "object") {
      console.log(values[index]);
      // console.log(`{${key}: ${values[index]}}`);

      stringifiedObj  = `${key}: ${values[index]}\n`;

      console.log("strinbgify inside if".toLocaleUpperCase(), stringifiedObj);
    } else {
      console.log("this is Object", obj[key]);
      stringify(obj[key], symbol, values, stringifiedObj);
    }
  });

  return `{${stringifiedObj}}`;
};

console.log("FUNCTION RETURN", stringify(data, "|-", 2, ""));


You can ignore symbol and numrepeat parameters since I will use them later.

so the expected result is

hello: world
is: true
nested:{
count: 5 
}

but it returns;

hello: world
is: true

where am I doing wrong?

CodePudding user response:

This should be a fixed version. I added some comments in the code.

Basically with recursion you usually need to return a value that will be grabbed in the nested iteration.

Array.ForEach does no return anything, just executes code of each item. Array.map instead, returns the result.

const data = { hello: "world", is: true, nested: { count: 5 } };

const stringify = (obj, symbol, numRepeat, stringifiedObj) => {
  const keys = Object.keys(obj);

  const values = Object.values(obj);

  // Here you need to RETURN something, so array.map maps an array into something else and you can return a value
  return keys.map((key, index) => {
    if (typeof values[index] !== "object") {

      // so return here the .map iteration
      return stringifiedObj   `${key}: ${values[index]}\n`; // You really need that \n at the end?
    } else {
      // else, return your other string
      return stringify(obj[key], symbol, values, stringifiedObj);
    }
  });

  // as we did -return keys.map- this one below is no longer needed
  //return stringifiedObj;
};

console.log("FUNCTION RETURN", stringify(data, "|-", 2, ""));

CodePudding user response:

You might like this simple type analysis technique using switch on t?.constructor. Each case performs only what is necessary for each type's "shape" and recursively call stringify on the type's child elements. By adding a default depth = 1 parameter, we are able to format the output using properly indented lines =

function stringify (t, depth = 1) {
  switch (t?.constructor) {
    case String:
      return `"${t.replace(/"/g, '\\"')}"`
    case Object:
      return `{${linebreak(depth)}${
        Object
          .entries(t)
          .map(([k,v]) => `${k}: ${stringify(v, depth   1)}`)
          .join(`,${linebreak(depth)}`)
      }${linebreak(depth - 1)}}`
    case Array:
      return `[${linebreak(depth)}${
        t
          .map(v => stringify(v, depth   1))
          .join(`,\n${"  ".repeat(depth)}`)
      }${linebreak(depth - 1)}]`
    default:
      return String(t)
  }
}

function linebreak (depth, s = "  ") {
  return `\n${s.repeat(depth)}`
}

const data = 
  { hello: "world", is: true, nested: { count: 5, any: null, with: [ "array", undefined, { object: NaN } ] } }

console.log(stringify(data))


{
  hello: "world",
  is: true,
  nested: {
    count: 5,
    any: null,
    with: [
      "array",
      undefined,
      {
        object: NaN
      }
    ]
  }
}
  • Related