skip to Main Content

Okay so I am trying to replicate JSON.stringify. Currently I am getting this error. Unfortunately this error is not very helpful because the message is cut short before it actually gets to what the problem is so I am quite stuck. Can anyone tell me what is wrong with my object code? I am currently commenting out the recusive code for nested arrays/objects as the tests for that are separate. error
Here is the code (only the code relevant to arrays and objects):

if (Array.isArray(input)) {
  var toJSON = '[';
  var key = 0;
  function toString() {

    if (input.length === 0) {
      return '[]';
    }

    if (key < input.length) {

      // if (Array.isArray(input[key]) || typeof input[key] === 'object') {
      //   toJSON += stringifier(input[key]);
      // }
      if (input[key] === null || typeof input[key] === 'function' || input[key] === undefined) {
        toJSON += 'null';
      }
      if (typeof input[key] === 'number' || typeof input[key] === 'boolean') {
        toJSON += `${input[key]}`;
      }
      if (typeof input[key] === 'string') {
        toJSON += `"${input[key]}"`;
      }
      key++;
      if (key < input.length) {
        toJSON += ',';
      }
      toString();
    }
  }
  toString();
  return toJSON + ']';
}

if (typeof input === 'object') {
  if (Object.keys(input).length === 0) {
    return '{}';
  }

  var toJSON = '{';
  for (var key in input) {
    toJSON += `"${key}":`;
    // if (Array.isArray(input[key]) || typeof input[key] === 'object') {
    //   toJSON += stringifier(input[key]);
    // }
    if (input[key] === null || input[key] === undefined || typeof input[key] === 'function') {
      toJSON += `null`;
    } else if (typeof input[key] === 'number') {
      toJSON += `${input[key]}`;
    } else if (typeof input[key] === 'string') {
      toJSON += `"${input[key]}"`;
    }
    if (key != Object.keys(input)[Object.keys(input).length - 1]) {
      toJSON += ',';
    }
  }
  return toJSON + '}';
}

Now if I uncomment the recursive code, it breaks the array code that is currently working and I get this: error 2

2

Answers


  1. There are several issues with your code

    1. in your object serialization (and seemingly also in your testcases) you forgot to handle booleans. Ie, you will output the property’s name, but not its value. Thus

      stringifier({"a": true})
      

      will produce an output of {"a": } which is obviously wrong

    2. You are serializing undefined to null. That’s not standard behaviour. Not sure whether your project is defined that way, but

       JSON.stringify({"a": undefined}) 
      

      will produce {} whereas your code produces {"a": null}

    3. In both of your code parts (ie for arrays and for objects) you are checking

       if (... || typeof input[key] === 'object')
          toJson += stringifier(input[key]);
      

      in the very beginning. The problem with that is, that typeof null === "object" ie, your recursive call will get an input of null which probably leads to erroneous results.

      I’d suggest to handle all primitives (number, string, boolean, undefined, null) first. And only if none of them applies, treat it as object or array …

    4. For adding the comma, I’d suggest a different approach. Instead of checking if there is an additional element/property to handle and add the comma at the end of the current iteration’s body, move this code to the beginning of the iteration and check if the current iteration is the very first iteration.

       let first = true;
       for (let key in input) {
         if (!first) toJson += ',';
         first = false;
         toJSON += `"${key}":`;
         ...
       }
      
    5. You may need special treat for some special objects like Date or Error, because handling them like standard objects may lead to undesired results. Ie for instance

       JSON.stringify(new Date());
      

      produces an ISO date string like "2023-07-06T13:08:29.460Z"

    And just as a general remark: It would probably be better, to just handle the Array and object specific parts separately and handle the serialization of primitive values in a single point. Ie currently you have to duplicate the code for serializing number, string or boolean which reduces maintainability and is (as your case shows) error prone. I’d suggest something like the following.

    function serialize(input, inarray) {
      switch (typeof input) {
        case "number":
        case "boolean": 
          return `${input}`;
        case "string":
          return `"${input}"`;
        case "undefined":
          //special behaviour of JSON.serialize 
          //it will serialize undefined array elements as "null"
          return inarray ? "null" : undefined;
        case "function":
          //functions don't show up in serialization output
          return undefined;
    
         //for the behaviour in your code, ie null, undefined and function
         //all being serialzed to null, you can just use the following
         //case "function":
         //case "undefined":
         //  return "null";
      }
    
      if (input === null)
        return "null";
    
      //now you can be sure, it's an object or array
    
      if (Array.isArray(input)) {
        let s = "["; let first = true;
        for (let e of input) {
          let ser = serialize(e, true);
          if (ser === undefined) 
            continue;
          if (!first) s+=", ";
          first = false;
          s += serialize(e);
        }
        return s + "]";
      } else {
        let s = "{"; let first = true;
        for (let k in input) {
          let ser = serialize(input[k]);
          if (ser === undefined)
            continue;
    
          if (!first) s+=","; 
          first = false;
          s += `"${k}": ${ser}`;
        }
        return s + "}";
      }
    }
    
    

    It’s quick and dirty. There may be some issues with special cases, but you get the idea. One issue with this code is, it doesn’t properly handle strings containing double quotes. Ie something like serialize({a:'a " string'}) leads to an erroneous JSON string …

    Login or Signup to reply.
  2. JavaScript datum are represented with objects (compound) and primitive data elements which can be nested to any conceivable depth. A recursive procedure is best suited to match the recursive structure of the input data.

    Recursion is a functional heritage and so using it with functional style yields the best results. This means avoiding things like mutation, variable reassignments, and other side effects. In the code below, we perform a simple type analysis of the input t, and immediately return the computed JSON representation –

    function serialize(t) {
      if (t === undefined)
        return undefined
      else if (t === null)
        return 'null'
      else if (t.constructor === String)
        return '"' + t.replace(/"/g, '\"') + '"'
      else if (t.constructor === Number)
        return String(t)
      else if (t.constructor === Boolean)
        return t ? 'true' : 'false'
      else if (t.constructor === Date)
        return t.toISOString()
      else if (t.constructor === Array)
        return '[' + t.map(v =>
          v === undefined ? 'null' : serialize(v)
        ).join(',') + ']'
      else if (t.constructor === Object)
        return '{' + Object.keys(t).flatMap(k =>
          t[k] === undefined
            ? []
            : [serialize(k) + ':' + serialize(t[k])]
        ).join(',') + '}'
      else
        throw Error(`cannot serialize unsupported type: ${t}`)
    }
    
    console.log(serialize(null))
    console.log(serialize('she said "hello"'))
    console.log(serialize(5))
    console.log(serialize([1,2,"three",true,false]))
    console.log(serialize({a:1,b:2,c:new Date}))
    console.log(serialize([{a:1},{b:2},{c:3}]))
    console.log(serialize({a:[1,2,3],c:[4,5,6,{seven:8}]}))
    console.log(serialize({a:undefined,b:2}))
    console.log(serialize([[["alice","bob",4,["charlie"]],3,4]]))
    .as-console-wrapper { min-height: 100%; top: 0; }

    See this Q&A for the original implementation.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search