2

Let's say I have this js object:

{"a": null, "b": 5, "c": 0, d: "", e: [1,2,null, 3]}

This is what I would like to get:

{"a": "null", "b": 5, "c": 0, d: "", e: [1,2,"null", 3]}

The only idea I have is to use:

function nullToString(v) {
    return JSON.parse(JSON.stringify(v).split(":null").join(":\"null\""));
}

But it seems like a quite expensive operation comparing with what I would like to achieve.

If there is any helpful method from jQuery or underscore.js that would be great.

mnowotka
  • 16,430
  • 18
  • 88
  • 134
  • 4
    You can always do it manually by iterating through all object keys (and "subkeys" and so on). It will probably be the fastest solution. – Regent Sep 24 '14 at 12:42
  • I was hoping for some non-imperative solution... – mnowotka Sep 24 '14 at 12:45
  • Have a look at: [how-to-iterate-json-hashmap][1] [1]: http://stackoverflow.com/questions/9007065/how-to-iterate-json-hashmap – Stefan Friedrich Sep 24 '14 at 12:46
  • You have already shown short solution. In this situation you should choose between fast work and short code. – Regent Sep 24 '14 at 12:46
  • 1
    Try: `JSON.parse(JSON.stringify(...).split("null,").join("\"null\"");`. This `split-join` approach has yielded better results than `replace` in my case. – Vikram Deshmukh Sep 24 '14 at 12:55
  • The replace method is much faster and less expensive than looping through the object and replacing the values. – Spokey Sep 24 '14 at 12:56
  • @Spokey have you profiled that? On Chrome 37, 10m iterations. *Recursive Iterate:* 25.176s, *Stringify/Replace/Parse:* 33.659s, *Stringify/Split/Join/Parse:* 59.692s – klyd Sep 24 '14 at 13:17
  • Why is this tagged as JSON? Do you actually want to produce JSON in the end? – Felix Kling Sep 24 '14 at 13:36
  • @FelixKling - this is tagged as JSON because my proposed answer uses `JSON.parse` and `JSON.stringify`. – mnowotka Sep 24 '14 at 13:53
  • The `.replace()` is inaccurate; e.g. it doesn't work for `a: null` or `, null` so any comparisons are kind of worthless imo. – Ja͢ck Sep 25 '14 at 00:33

2 Answers2

3

This works for the data you provided:

var data = {"a": null, "b": 5, "c": 0, d: "", e: [1,2,null, 3]};

function nullToString(value) { 

    function recursiveFix(o) {
        // loop through each property in the provided value
        for(var k in o) {
            // make sure the value owns the key
            if (o.hasOwnProperty(k)) { 
                if (o[k]===null) {
                    // if the value is null, set it to 'null'
                    o[k] = 'null';
                } else if (typeof(o[k]) !== 'string' && o[k].length > 0) {
                    // if there are sub-keys, make a recursive call
                    recursiveFix(o[k]);
                }
            }
        }
    }

    var cloned = jQuery.extend(true, {}, value)
    recursiveFix(cloned);
    return cloned;
}

console.log(nullToString(data));

Basic premise is to recursively loop over your object's properties and replace the value if its null.

Of course the root of your question is "I want something faster." I invite you to profile your solution, this one, and any others you come across. Your results may be surprising.

klyd
  • 3,939
  • 3
  • 24
  • 34
  • http://jsfiddle.net/euepw79d/ - too much recursion problem when keys are strings... – mnowotka Sep 24 '14 at 13:45
  • @mnowotka I think that was for another answer... Further the function doesn't return anything. It modifies the original value. So `console.log(nullToString(value))` will always return undefined. But `nullToString(value); console.log(value);` will have your updated values. – klyd Sep 24 '14 at 14:06
  • Oh, I see. I need a function returning something instead of modifying object in place so I can use it as a decorator. – mnowotka Sep 24 '14 at 14:09
  • Easy enough, just clone the object. See my update. I'm using `jQuery.extend()` for the clone, but you can also use [`_.clone()`](http://underscorejs.org/#clone) if you want. – klyd Sep 24 '14 at 14:18
  • Cloning does make this significantly slower (on par with the Stringify/Split/Join/Parse solutions). if you don't mind that the original object is modified then the cloning is no longer necessary and you can regain the lost speed by simply returning the object when done with it. – klyd Sep 24 '14 at 14:26
  • After all the changes you have made, how is execution time compared to http://jsfiddle.net/ppLp2Lz0/? – mnowotka Sep 24 '14 at 14:26
  • exactly, cloning makes it slower but this is the form which I wanted to achieve (i mean function that returns something) so only now comparing time with stringify/parse/split/replace would make sense and be fair. – mnowotka Sep 24 '14 at 14:28
  • Given your restraints, I think what you have is about as fast as you're going to get right now. You may consider updating the OP. – klyd Sep 24 '14 at 14:42
  • What OP currently has is wrong, this is the correct and reliable approach, so +1 from me. – Ja͢ck Sep 25 '14 at 00:35
0

Here is a very simple example:

function convertObjectValuesRecursive(obj, target, replacement) {
 obj = {...obj};
 Object.keys(obj).forEach((key) => {
  if (obj[key] == target) {
   obj[key] = replacement;
  } else if (typeof obj[key] == 'object' && !Array.isArray(obj[key])) {
   obj[key] = convertObjectValuesRecursive(obj[key], target, replacement);
  }
 });
 return obj;
}

The function takes three parameters, the obj, the target value, and the replacement value, and will recursively replace all target values (including values in nested objects) with the replacement value.

S. Heutmaker
  • 146
  • 1
  • 6