Skip to content

Instantly share code, notes, and snippets.

@tsaniel
Forked from 140bytes/LICENSE.txt
Created July 17, 2011 07:37
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tsaniel/1087317 to your computer and use it in GitHub Desktop.
Save tsaniel/1087317 to your computer and use it in GitHub Desktop.
JSON stringify

JSON stringify

A simple JSON stringify function which requires Array.prototype.map method.

Contributors

The following users provided lots of ideas and improvements of the code.

@Kambfhase, @hartrock, @atk, @haochi, @jed

Actually, I just provided a deficient version at first. The other versions were created by these users(especially @atk). You may see the discussion below.

function f(
a, // input (expect object)
b, // placeholder
c // placeholder
){
for (b in
// if the input is an object, assign an empty array to c and loop through the properties of it;
// otherwise assign false to c and iterate over nothing.
// but since it's just a roughly checking, a string "[object Object]" will falsely be identified as object,
// and starts the iteration in most of the browsers -
// as properties on strings can be accessed like arrays(except lower IE version, e.g. IE7).
// (reference: http://kangax.github.com/es5-compat-table/#property-access-on-strings).
// anyway, the function still works although there may be unwanted loopings with a bit slower.
(c = a == '' + {} && []) && a
)
// fill array with object-style key: value pairs, call ourself recursively to escape keys and values.
c.push(f(b) + ':' + f(a[b]));
// check if the input is a string,
return '' + a === a ?
// output as a string literal;
'"' + a + '"' :
// otherwise check if the input is present and an array (has .map),
a && a.map ?
// output as an array literal;
'[' + a.map(f) + ']' :
// otherwise check if the input is an object (c is an array rather than false),
c ?
// output as an object literal;
'{' + c + '}' :
// otherwise use plain coercion (mostly for number/boolean/null literals).
a
}
function f(a,b,c){for(b in(c=a==""+{}&&[])&&a)c.push(f(b)+":"+f(a[b]));return""+a===a?'"'+a+'"':a&&a.map?"["+a.map(f)+"]":c?"{"+c+"}":a}
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2011 YOUR_NAME_HERE <YOUR_URL_HERE>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
{
"name": "JSON stringify",
"description": "A JSON stringify function",
"keywords": [
"JSON",
"stringify"
]
}
<!DOCTYPE html>
<title>JSON stringify</title>
<div>Expected value: <b>{"a":123,"b":true,"c":false,"d":null,"e":{"a":"some text"},"f":[1,2,3],"g":["eins","zwei","drei"],"h":[{"one":1},{"one":1,"two":2}],"i":[{"one":1},{"one":1,"two":[{"three":3},[1,2,3]]}]}</b></div>
<div>Actual value: <b id="ret"></b></div>
<script>
var myFunction = function f(a,b,c){for(b in(c=a==""+{}&&[])&&a)c.push(f(b)+":"+f(a[b]));return""+a===a?'"'+a+'"':a&&a.map?"["+a.map(f)+"]":c?"{"+c+"}":a};
document.getElementById( "ret" ).innerHTML = myFunction({
a: 123,
b: true,
c: false,
d: null,
e: {a: "some text"},
f: [1, 2, 3],
g: ["eins", "zwei", "drei"],
h: [{one:1}, {"one":1, two:2}],
i: [{one:1}, { "one":1, two:[ {three:3}, [1,2,3] ] } ]
})
</script>
@Kambfhase
Copy link

Removed the last ''+. It should automatically call toString().

If you just pass in a number now, it will not be converted to a string any more.

@atk
Copy link

atk commented Jul 21, 2011

same goes for empty Object, null, true/false, undefined.

@tsaniel
Copy link
Author

tsaniel commented Jul 21, 2011

It's funny, because it seems working well to me with the following testing.

var input = {
    a: 123,
    b: true,
    c: false,
    d: null,
    e: {a: "some text"},
    f: [1, 2, 3],
    g: ["eins", "zwei", "drei"],
    h: [{one:1}, {"one":1, two:2}],
    i: [{one:1}, { "one":1, two:[ {three:3}, [1,2,3] ] } ]
};
(function f(a,b,c,d){c=''+a!==a&&[];for(d in c?a:0)c.push(f(d)+':'+f(a[d]));return a&&a.map?'['+a.map(f)+']':d?'{'+c+'}':c?a:'"'+a+'"'})(input) === JSON.stringify(input)

@atk
Copy link

atk commented Jul 21, 2011

Sure, but you already had some filled object that provided string format in the first place:

(function f(a,b,c,d){c=''+a!==a&&[];for(d in c?a:0)c.push(f(d)+':'+f(a[d]));return a&&a.map?'['+a.map(f)+']':d?'{'+c+'}':c?a:'"'+a+'"'})(null) === JSON.stringify(null) // false

@tsaniel
Copy link
Author

tsaniel commented Jul 21, 2011

Ah, didn't notice that. I have never used it in this way .

@hartrock
Copy link

In http://www.json.org/ it's not totally clear, but in http://www.ietf.org/rfc/rfc4627.txt?number=4627 there is stated:
+++
2. JSON Grammar

A JSON text is a sequence of tokens. The set of tokens includes six
structural characters, strings, numbers, and three literal names.

A JSON text is a serialized object or array.

  JSON-text = object / array

These are the six structural characters:

  begin-array     = ws %x5B ws  ; [ left square bracket

  begin-object    = ws %x7B ws  ; { left curly bracket

  end-array       = ws %x5D ws  ; ] right square bracket

  end-object      = ws %x7D ws  ; } right curly bracket

+++
So there is no need for "null", which Firefox's JSON.stringify(null) reveals.
But there remains a need for "{}": 6 bytes left for that...

@atk
Copy link

atk commented Jul 24, 2011

I basically dislike a function to fail on whatever input - but I'd rather it fails harmless than throwing an error like it would if we did not care for null.

@hartrock
Copy link

Result f({}) -> Object { } is wrong for a valid input (which null is not); it even does not return a string, which is what this method is supposed to do.
OK, this is not so hard as in other languages, but it is not parseable by JSON.parse().

@atk
Copy link

atk commented Jul 24, 2011

Sorry, both my firefox 5.0 as my node 4.10 installation do indeed return the string "null" if called with null as parameter. Anyway, if we take out the a&& of a&&a.map, we will soon see is was necessary to avoid complete and utter failure on null or undefined anywhere within the input - plus we still wouldn't save enough space.

We could probably save another byte by replacing the test?a:0 with test&&a within the for-loop.

@atk
Copy link

atk commented Jul 24, 2011

OK, got it down to 140bytes anyway... by reorganizing the test structure, I golfed away the final crucial byte:

function f(a,b,c){for(b in(c=/t]/.test(a)&&[])&&a)c.push(f(b)+':'+f(a[b]));return''+a===a?'"'+a+'"':a&&a.map?'['+a.map(f)+']':c?'{'+c+'}':a}

I'll leave it to you to a) create the annotated version b) applaud us (I'd never made this alone) on this achievement.

@tsaniel
Copy link
Author

tsaniel commented Jul 25, 2011

Thank you all. You guys are very awesome! And i made the changes.

@atk
Copy link

atk commented Jul 25, 2011

A very interesting feat is the intentional defective object detection in this case. A string containing "t]" will falsely be identified as object and starts the iteration. The iterated characters "t" and "]" will not restart the iteration themselves, therefore unwanted recursion will not happen and the function may be a bit slower, but works without failure nevertheless.

I'd like to see some more informations in the annotated version about this.

Update: thank you!

@tsaniel
Copy link
Author

tsaniel commented Jul 25, 2011

It's really interesting, although this will not happen in lower IE versions.

@atk
Copy link

atk commented Jul 25, 2011

This function will still fail on escaped special characters like \n or \t :-(

http://twitter.com/garethheyes/status/95511347042856960

@tsaniel
Copy link
Author

tsaniel commented Jul 26, 2011

I have to admit that it's hard to do so in 140 bytes :-(
Maybe the escaped special characters can be escaped themselves first, like:
\n to \\n.

@jed
Copy link

jed commented Jul 26, 2011

i'm kinda late to this one, but can't you save two bytes by turning

/t]/.test(a)

into

(a=={}+"")

?

@atk
Copy link

atk commented Jul 26, 2011

nice idea! Yes, that would probably work:

function f(a,b,c){for(b in(a==''+{}&&[])&&a)c.push(f(b)+':'+f(a[b]));return''+a===a?'"'+a+'"':a&&a.map?'['+a.map(f)+']':c?'{'+c+'}':a}

@tsaniel
Copy link
Author

tsaniel commented Jul 26, 2011

@jed Thanks, the tip is great! I think it should be written in the Byte-Saving Techniques.
@atk Thanks as well. But I think the code is missing something...

@jed
Copy link

jed commented Jul 26, 2011

@atk are you sure about that operator precedence?

@atk
Copy link

atk commented Jul 26, 2011

@jed + > == > &&, so this should work, yes. I have to admit I printed out the sheet of the precedence order from mozilla documentation.

@michaelficarra
Copy link

So.... nobody seems to have mentioned the failure case with strings containing ".

@atk
Copy link

atk commented Jul 27, 2011

It was obvious to us that the contents of strings are not correctly escaped, so we didn't mention it. We're still in the process of golfing for more space for the necessary filter.

@tkissing
Copy link

'' + a === a could be shortened to a && a.sub (or am I missing something?)

@Kambfhase
Copy link

that will disable support for non-browser environments like (Node-js) Rhino

@tkissing
Copy link

Ah, right. Bummer. And .trim won't work in older JS implementations. Too bad...

@tsaniel
Copy link
Author

tsaniel commented Nov 11, 2011

And so is Array#map...

@atk
Copy link

atk commented Nov 11, 2011

But Array#map will work in node.js - and for older implementations we already have a working polyfill.

@tsaniel
Copy link
Author

tsaniel commented Nov 11, 2011

In that way, we have polyfill for trim as well.

@adius
Copy link

adius commented Mar 7, 2012

You should use a.pop instead of a.map as in DOMinate. Much better browser compatibility!

@atk
Copy link

atk commented Mar 9, 2012

@adius: doesn't help, because we'll need a.map anyway. A working version (alas, with 214bytes too long) is here:

function s(a,b,c){for(b in(c=a==''+{}&&[])&&a)c.push(s(b)+':'+s(a[b]));return''+a===a?'"'+a.replace(/[\x00-\x32\\]/g,function(x){return'\x'+x.charCodeAt().toString(16)})+'"':a&&a.map?'['+a.map(s)+']':c?'{'+c+'}':a}

I've not yet tested if it still works if you replace function(x){return'\x'+x.charCodeAt().toString(16)} with function(x){return escape(x).replace(/%/g,'\\x')}, saving 2 further bytes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment