Created
February 15, 2023 19:02
-
-
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 )
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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