3

I have an object, i.e.:

{
  item1: "value1",
  item2: "value2",
  item3: { 
      item4: "value4",
      item5: "value5" 
  }
}

I want to use JSON.stringify with a replacer function that will act different on items 4 & 5, the inner properties of item3.
How can that be done ?

something like the following pseudo-code:

     return JSON.stringify(obj, (key, val) => {
         if (key is child of Item3) {
             return someOtherValue;
         } else {
             return val; 
         } 
}

The desired output is json. i.e.:

{ 
  "item1" : "value1", 
  "item2" : "value2", 
  "item3" : { 
      "item4" : "theSomeOtherValue", 
      "item5" : "theSomeOtherValue"
}

Edit:
Items 4 & 5 are not known beforehand, they are dynamically generated.
I only know the title for item3 at run time

codeKnight
  • 241
  • 2
  • 12

3 Answers3

8

There are at least two approaches you can take here.

  1. In the replacer function, this is the object being processed, so when item4 and item5 are being processed, this refers to the item3 object. So if there's anything about that object that lets you identify it, you can do that by looking at this. (Be sure to use a traditional function, not an arrow functions, so that JSON.stringify can set what this is during the replacer call.)

  2. The replacer function is called with the key and value being processed, so if the key of the object ("item3") is unique, you could do special processing when you see it.

Here are a couple of examples of #1:

For instance, if you have a reference to the object, you can compare this to obj.item3:

const obj = {
    item1: "value1",
    item2: "value2",
    item3: { 
        item4: "value4",
        item5: "value5" 
    }
};
console.log(JSON.stringify(obj, function(key, value) {
//                              ^^^^^^^^^−−−−− Traditional function, not arrow function
    if (this === obj.item3) {
        console.log("do something different, it's " + key);
        return "theSomeOtherValue";
    }
    return value;
}));

If you don't have a reference to it, you can use any other identifying information you have about it. For instance, with the example data, you can see that it has item4 and item5 properties:

console.log(JSON.stringify({
    item1: "value1",
    item2: "value2",
    item3: { 
        item4: "value4",
        item5: "value5" 
    }
}, function(key, value) {
// ^^^^^^^^^−−−−− Traditional function, not arrow function
    if (this.hasOwnProperty("item4") && this.hasOwnProperty("item5")) {
        console.log("do something different, it's " + key);
        return "theSomeOtherValue";
    }
    return value;
}));

Those are just two examples, though; the key thing is that this is the object being stringified.

Here's an example of #2:

console.log(JSON.stringify({
    item1: "value1",
    item2: "value2",
    item3: { 
        item4: "value4",
        item5: "value5" 
    }
}, (key, value) => { // It's okay if this one is an arrow function, we're not relying on
                     // `JSON.stringify` setting `this` for us
    if (key === "item3") {
        return {
            item4: "theSomeOtherValue",
            item5: "theSomeOtherValue"
        };
    }
    return value;
}));

If you need the full path of the object being processed, that's a bit more of a pain, but you can get it:

let paths = new Map();
console.log(JSON.stringify({
    item1: "value1",
    item2: "value2",
    item3: { 
        item4: "value4",
        item5: "value5" 
    },
    item6: {
        item3: {
            item4: "non-special item4",
            item5: "non-special item5"
        }
    }
}, function(key, value) {
// ^^^^^^^^^−−−−− Traditional function, not arrow function
    const path = paths.get(this);
    // Special processing for the properties of root.item3
    if (path === "root.item3") {
        return key === "item4" || key === "item5"
            ? "theSomeOtherValue"
            : value;
    }

    // Keep track of the path of the object
    for (const [k, v] of Object.entries(this)) {
        if (typeof v === "object") {
            if (path) {
                // The regex checks for property names that aren't valid(ish)
                // property names so we can use brackets notation
                paths.set(v, path + (/^\w+$/.test(k) ? "." + k : `[${JSON.stringify(k)}]`));
            } else {
                paths.set(v, "root");
            }
        }
    }
    return value;
}));
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
0

JSON.stringify accepts a replacer function as 2nd argument (and provides the current object being iterated as this) so you can do:

return JSON.stringify(obj, function(key, val) {
    if (this === obj.item3) {
        return "theSomeOtherValue";
    }
    return val;
});

Another way to achieve the same (using current key instead of this object reference):

return JSON.stringify(obj, function(key, val) {
    if (key === "item4" || key === "item5") {
        return "theSomeOtherValue";
    }
    return val;
});
chiragrtr
  • 902
  • 4
  • 6
  • 1
    This answer has already been posted, several minutes ago. Please check for previous answers before answering, so you don't just repost answers that have been given. – T.J. Crowder May 23 '20 at 09:46
0

You could store the found wanted object in a closure and change the return value depening on the object.

This approach works without this.

function replacer(parent) {
    let reference = {};
    return function (key, val) {
        if (key === parent) reference = val;
        return key in reference
            ? '#' + val
            : val; 
    };
}

var obj = { item1: "value1", item2: "value2", item3: { item4: "value4", item5: "value5" } },
    json = JSON.stringify(obj, replacer('item3'));

console.log(json);
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392