TL;DR -- The Code
With a caveat, below, about lookbehind support in browsers, here you go:
function cleanIt(obj) {
var cleaned = JSON.stringify(obj, null, 2);
return cleaned.replace(/^[\t ]*"[^:\n\r]+(?<!\\)":/gm, function (match) {
return match.replace(/"/g, "");
});
}
NOTE: Lookbehinds aren't supported on IE or, strangely, any version of Safari as of 18 June 2021. To use there, remove (?<!\\)
from the regex and watch out for the "gotcha" condition mentioned below.
That stringifies with prettiness, uses our regex to find keys, then each key match is replaced via a replacer function that says "here's your match with all double quotes removed". Since we only matched keys up to their colons, that's exactly what we want.
Detailed description below.
Why the accepted answer is not all right
Unfortunately, as Adel points out points out, there's a pretty serious issue with Derek's regular expression-less solution. It doesn't handle arrays of objects. You see how it shorts out and says, "If it's an array, let JSON.stringify
handle it"? That's fine for simple value types, but not for objects.
// \/\/\/ OH NOES!!! \/\/\/
if (typeof obj_from_json !== "object" || Array.isArray(obj_from_json)){
// not an object, stringify using native function
return JSON.stringify(obj_from_json);
// ^^^^^^^^^^^^^^ No! Don't do that!
}
I edited his fiddle with a fail case. Here's the payload:
var obj = {
name: "John Smith",
favoriteObjects: [
{ b: "there's", c: "always", d: "something" },
[1, 2, "spam", { f: "hello" }],
{ vowels: "are missing" },
],
favoriteFruits: ["Apple", "Banana"],
};
And here's the whitespaced output:
{
name:"John Smith",
favoriteObjects:[
{
"b":"there's", <<< Major
"c":"always", <<< fails
"d":"something" <<< here
},
[
1,2,"spam",
{
"f":"hello" <<< and here.
}
],
{
"vowels":"are missing" <<< again.
}
],
favoriteFruits:["Apple","Banana"] <<< that worked!
}
See? Though the array of strings (favoriteFruits
) works, we've still got the quotes on the second level of serialization for objects in arrays (like favoriteObjects
). Bad.
A simple solution
I spent waaay too much time trying to clean up that function before figuring out a slick trick to reduce the complexity of the problem considerably, which I think brings regex back into the mix.
Let's stringify
with whitespace
In the vein of Mr. McGuire, I just want to say one word to you, just one word:
Whitespace.
Let's JSON.stringify
with some whitespace. That makes the regex to remove the quotes from the keys MUCH easier.
Start your solution with this:
var cleaned = JSON.stringify(x, null, 2);
The call to stringify
says "stringify x
(the first param) without a fancy replacer function (indicated by the second param of null
) with two (3rd param of 2
) spaces of whitespace for each level of depth in the serialization." That 2
also buys us separate lines for each property.
{
"name": "John Smith",
"favoriteObjects": [
{
"b": "there's",
"c": "always",
// etc...
Every key is now nicely at the start of a line. That we can handle.
NOW clean the quotes off of keys
Let's create a regex that looks for keys, and only keys, and pulls off their surrounding "
. Since we know that keys are all on their own lines now, things are much simpler.
The only real gotcha is if the value to a property contains ":
midway through. That can screw things up.
"a": [
"This could \": be bad",
"QQ"
]
Lookbehind Caveat
I wanted to solve this with a fancy negative look-ahead for older browsers, but gave up and used a negative lookbehind. Negative lookbehind is only supported by a subset of browsers after 2018 (description of history of lookbehind proposal at 2ality here, browser compatibility can be found here).
RegEx Explained:
/^[\t ]*"[^:\n\r]+(?<!\\)":/gm
That regex...
- Is a regex
- Looks for any line that starts with (0-to-infinity spaces or tabs)
- Then a
"
- Then one or more characters of anything that ISN'T a
:
or a newline character
- Then that ends with the two characters
":
that (negative lookbehind warning) isn't preceded by a backslash
- Does this globally and multi-line (line by line)
Result:
{
name: "John Smith",
favoriteObjects: [
{
b: "there's",
c: "always",
d: "something"
},
[
1,
2,
"spam",
{
f: "hello"
}
],
{
vowels: "are missing"
}
],
favoriteFruits: [
"Apple",
"Banana"
]
}
QED a short eight and a half years after jadent asked. Now that's service!
Quick Update: An even better answer, if you don't necessarily need this to happen in code (though in some use cases, you could call this library directly too), could be to use prettier, which cleans things beautifully.