Skip to content

Instantly share code, notes, and snippets.

@DarrenSem
Created February 15, 2023 19:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DarrenSem/e17e88afb16f133876fefb6547f181ad to your computer and use it in GitHub Desktop.
Save DarrenSem/e17e88afb16f133876fefb6547f181ad to your computer and use it in GitHub Desktop.
JSONstringify.js - from-scratch implementation of JSON.stringify as a coding exercise ( and for JScript WSH using CSCRIPT.EXE /nologo MyJScript.js )
// JSONstringify.js - from-scratch implementation of JSON.stringify as a coding exercise ( and for JScript WSH using CSCRIPT.EXE /nologo MyJScript.js )
// 525 chars _=function(a){var b,c=null,d=typeof a,e=/^(undefined|function|bigint|symbol)$/i,f=0,g=[],h=function(a,b){return a==b?a+"":{}.toString.call(a).slice(8,-1)};if(!e.test(d)){if("string"===d)return"\""+a+"\"";if(!a||"object"!==d||"Number"===h(a))return("Number"===h(a)?isFinite(a)?+a:c:a)+"";if("Date"===h(a)&&!isNaN(a))return _(a.toISOString());if("Array"===h(a)){for(b=a.length;f<b;f++)g.push(_(e.test(h(a[f]))?c:a[f]));return"["+g+"]"}for(f in a)a.hasOwnProperty(f)&&!e.test(h(a[f]))&&g.push(_(f)+":"+_(a[f]));return"{"+g+"}"}}
var JSONstringify = function(obj) {
var NULL = null;
var type = typeof obj;
var skip = /^(undefined|function|bigint|symbol)$/i
var L;
var key = 0;
var elements = [];
var ctor = function(obj, _UNDEF) {
return obj == _UNDEF ? String(obj) : {}.toString.call(obj).slice(8, -1);
};
if( skip.test(type) ) {
return;
};
if(type === 'string') {
return '"' + obj + '"';
};
if(!obj || type !== 'object' || ctor(obj) === "Number") {
return (
String(
ctor(obj) === "Number" ? isFinite(obj) ? +obj : NULL
: obj
)
);
};
if(ctor(obj) === "Date" && !isNaN(obj)) {
return JSONstringify(obj.toISOString());
};
if(ctor(obj) === "Array") {
for(L = obj.length; key < L; key ++) {
elements.push(
JSONstringify(
skip.test( ctor(obj[key]) ) ? NULL : obj[key]
)
);
};
return '[' + elements + ']';
};
for(key in obj) {
if( obj.hasOwnProperty(key) && !skip.test( ctor(obj[key]) ) ) {
elements.push(
JSONstringify(key) + ":" + JSONstringify(obj[key])
);
};
};
return "{" + elements + "}";
};
var stringify_compared_to_JSON = function(func) {
return [
func( null ), // => 'null'
func( true ), // => 'true'
func( false ), // => 'false'
func( 0.0 ), // => '0'
func( "0.0" ), // => "'0.0'"
func( [] ), // => '[]'
func( {} ), // => '{}'
func( location ), // => .match(/^(Location )?{.*\bhref:\s*".+}$/) != null , .match(/^{ancestorOrigins: '\[object DOMStringList\]'/) != null
func( Infinity ), // => 'null'
func( NaN ), // => 'null'
func( undefined ), // => undefined
func(), // => undefined
func( Date ), // => undefined
func( new TypeError("xyz") ), // => '{}'
func( /z/img ), // => '{}'
func( [ , ] ), // => '[null]'
func( new Array(3) ) // => '[null,null,null]'
/// >> comment out this section for JScript...
, func( new Map( [ ["3", 6.0], [9.0, "8.0"] ] ) ), // => '{}'
func( new Set( [3, 6.0, "9", Symbol(3)] ) ), // => '{}'
func( new Set( [ [3, 6] , [9, 12] ] ) ), // => '{}'
func( new WeakSet( [Symbol(3)] ) ), // => '{}'
func( new Int8Array(3) ), // => '{"0":0,"1":0,"2":0}'
func( new Int8Array( [ 3, 6, 9 ] ) ), // => '{"0":3,"1":6,"2":9}'
func( Symbol(3) ), // => undefined
func( x=>Symbol( x ) ), // => undefined
func === JSONstringify ? func( BigInt(3) ) : undefined // => undefined , instead of TypeError: Do not know how to serialize a BigInt
];
};
/*@cc_on @if(@_jscript) console={log:function(){var b=arguments;b.length&&WScript.Echo([].join.call(b," "))}}; location={href:WScript.ScriptFullName}; @end@*/
// ^ JScript needs these additional 158 char, to add these 2 Objects that exist in web browsers...
// console={log:function(){var a=arguments;a.length&&WScript.Echo([].join.call(a," "))}};
// location={href:WScript.ScriptFullName};
/*@cc_on @if(@_jscript) Date.prototype.toISOString=function(){var a=this,b="Month",c=function(c,d){var e=a["getUTC"+c]()+(c==b&&1)+"";return"0000".substr(0,(d||2)-e.length)+e};return c("FullYear")+"-"+c(b)+"-"+c("Date")+"T"+c("Hours")+":"+c("Minutes")+":"+c("Seconds")+"."+c("Milliseconds",3)+"Z"} @end@*/
// ^ ...plus these additional 305 char, to add Date.toISOString() which exists in modern JS engines / web browsers
var isWeb = Date.now;
if(isWeb)console.clear();
console.log(stringify_compared_to_JSON(JSONstringify));
if(isWeb) {
console.log(stringify_compared_to_JSON(JSON.stringify));
console.log(
"^ ^ same resulting strings?",
stringify_compared_to_JSON(JSONstringify).join("|") === stringify_compared_to_JSON(JSON.stringify).join("|")
);
};
// mine BEFORE obj.hasOwnProperty(key) ['null', 'true', 'false', '0', '"0.0"', '[]', '{}', '{"ancestorOrigins":{"length":0},"href":"https://gi…dmia/2883e2e8b321cd5cac37","search":"","hash":""}', 'null', 'null', undefined, undefined, undefined, '{}', '{}', '[null]', '[null,null,null]', '{}', '{}', '{}', '{}', '{"0":0,"1":0,"2":0}', '{"0":3,"1":6,"2":9}', undefined, undefined, undefined]
// mine AFTER obj.hasOwnProperty(key) ['null', 'true', 'false', '0', '"0.0"', '[]', '{}', '{"ancestorOrigins":{},"href":"https://gi…dmia/2883e2e8b321cd5cac37","search":"","hash":""}', 'null', 'null', undefined, undefined, undefined, '{}', '{}', '[null]', '[null,null,null]', '{}', '{}', '{}', '{}', '{"0":0,"1":0,"2":0}', '{"0":3,"1":6,"2":9}', undefined, undefined, undefined]
// JSON.stringify... ['null', 'true', 'false', '0', '"0.0"', '[]', '{}', '{"ancestorOrigins":{},"href":"https://gi…dmia/2883e2e8b321cd5cac37","search":"","hash":""}', 'null', 'null', undefined, undefined, undefined, '{}', '{}', '[null]', '[null,null,null]', '{}', '{}', '{}', '{}', '{"0":0,"1":0,"2":0}', '{"0":3,"1":6,"2":9}', undefined, undefined, undefined]
// ONE FINAL TEST: test that all these tests test successfully...
// https://github.com/mdn/content/pull/19595
// ...from the custom function type(value) from MDN for "typeof"...
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof#custom_method_that_gets_a_more_specific_type
var values = [
5,
-0,
new Number(5),
false,
null,
undefined,
{},
[],
new Date( "1 March 2001 1:02:03" + (isWeb ? ".456 am" : " am") ),
/abc/g,
{ constructor: { name: { something: 'terrible' } } }
];
/// >> comment out this section for JScript...
class Person {}
const mixinClass = (C) => class extends C {};
const MixPerson = mixinClass(Person);
values = values.concat([
5n, // BigInt
() => {},
async () => {},
function* () {},
Promise.resolve(),
new Set(),
new Map(),
new WeakMap(),
class {},
class Person {},
MixPerson,
new MixPerson(),
ArrayBuffer,
new ArrayBuffer(16),
Symbol.iterator,
[][Symbol.iterator](),
{
[Symbol.toPrimitive]() {
return 'good';
},
toString() {
throw new Error('bad');
}
},
]);
var L = values.length;
// to be consistent, skips BigInt for both, because JSON will throw TypeError: Do not know how to serialize a BigInt
var resultMine = Array(L);
for(var i = 0; i < L; i ++) {
if(typeof values[i] !== 'bigint')resultMine[i] = JSONstringify( values[i] );
};
var resultJSON = Array(L);
if(isWeb)for(var i = 0; i < L; i ++) {
if(typeof values[i] !== 'bigint')resultJSON[i] = JSON.stringify( values[i] );
};
console.log(resultMine);
if(isWeb) {
console.log(resultJSON);
console.log(
"^ ^ same resulting strings?",
resultMine.join("|") === resultJSON.join("|")
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment