123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498 |
- // tests stringify()
- /*global require console exports */
- // set to true to show performance stats
- var DEBUG = false;
- var assert = require('assert');
- var JSON5 = require('../lib/json5');
- // Test JSON5.stringify() by comparing its output for each case with
- // native JSON.stringify(). The only differences will be in how object keys are
- // handled.
- var simpleCases = [
- null,
- 9, -9, +9, +9.878,
- '', "''", '999', '9aa', 'aaa', 'aa a', 'aa\na', 'aa\\a', '\'', '\\\'', '\\"',
- undefined,
- true, false,
- {}, [], function(){},
- Date.now(), new Date(Date.now())
- ];
- exports.stringify = {};
- exports.stringify.simple = function test() {
- for (var i=0; i<simpleCases.length; i++) {
- assertStringify(simpleCases[i]);
- }
- };
- exports.stringify.oddities = function test() {
- assertStringify(Function);
- assertStringify(Date);
- assertStringify(Object);
- assertStringify(NaN);
- assertStringify(Infinity);
- assertStringify(10e6);
- assertStringify(19.3223e6);
- assertStringify(077);
- assertStringify(0x99);
- assertStringify(/aa/);
- assertStringify(new RegExp('aa'));
-
- assertStringify(new Number(7));
- assertStringify(new String(7));
- assertStringify(new String(""));
- assertStringify(new String("abcde"));
- assertStringify(new String(new String("abcde")));
- assertStringify(new Boolean(true));
- assertStringify(new Boolean());
- };
- exports.stringify.arrays = function test() {
- assertStringify([""]);
- assertStringify([1, 2]);
- assertStringify([undefined]);
- assertStringify([1, 'fasds']);
- assertStringify([1, '\n\b\t\f\r\'']);
- assertStringify([1, 'fasds', ['fdsafsd'], null]);
- assertStringify([1, 'fasds', ['fdsafsd'], null, function(aaa) { return 1; }, false ]);
- assertStringify([1, 'fasds', ['fdsafsd'], undefined, function(aaa) { return 1; }, false ]);
- };
- exports.stringify.objects = function test() {
- assertStringify({a:1, b:2});
- assertStringify({"":1, b:2});
- assertStringify({9:1, b:2});
- assertStringify({"9aaa":1, b:2});
- assertStringify({aaaa:1, bbbb:2});
- assertStringify({a$a_aa:1, bbbb:2});
- assertStringify({"a$a_aa":1, 'bbbb':2});
- assertStringify({"a$a_aa":[1], 'bbbb':{a:2}});
- assertStringify({"a$22222_aa":[1], 'bbbb':{aaaa:2, name:function(a,n,fh,h) { return 'nuthin'; }, foo: undefined}});
- assertStringify({"a$222222_aa":[1], 'bbbb':{aaaa:2, name:'other', foo: undefined}});
- assertStringify({"a$222222_aa":[1, {}, undefined, function() { }, { jjj: function() { } }], 'bbbb':{aaaa:2, name:'other', foo: undefined}});
-
- // using same obj multiple times
- var innerObj = {a: 9, b:6};
- assertStringify({a : innerObj, b: innerObj, c: [innerObj, innerObj, innerObj]});
- };
- exports.stringify.oddKeys = function test() {
- assertStringify({"this is a crazy long key":1, 'bbbb':2});
- assertStringify({"":1, 'bbbb':2});
- assertStringify({"s\ns":1, 'bbbb':2});
- assertStringify({'\n\b\t\f\r\'\\':1, 'bbbb':2});
- assertStringify({undefined:1, 'bbbb':2});
- assertStringify({'\x00':'\x00'});
- };
- // we expect errors from all of these tests. The errors should match
- exports.stringify.circular = function test() {
- var obj = { };
- obj.obj = obj;
- assertStringify(obj, null, true);
- var obj2 = {inner1: {inner2: {}}};
- obj2.inner1.inner2.obj = obj2;
- assertStringify(obj2, null, true);
- var obj3 = {inner1: {inner2: []}};
- obj3.inner1.inner2[0] = obj3;
- assertStringify(obj3, null, true);
- };
- exports.stringify.replacerType = function test() {
- var assertStringifyJSON5ThrowsExceptionForReplacer = function(replacer) {
- assert.throws(
- function() { JSON5.stringify(null, replacer); },
- /Replacer must be a function or an array/
- );
- };
- assertStringifyJSON5ThrowsExceptionForReplacer('string');
- assertStringifyJSON5ThrowsExceptionForReplacer(123);
- assertStringifyJSON5ThrowsExceptionForReplacer({});
- };
- exports.stringify.replacer = {};
- exports.stringify.replacer.function = {};
- exports.stringify.replacer.function.simple = function test() {
- function replacerTestFactory(expectedValue) {
- return function() {
- var lastKey = null,
- lastValue = null,
- numCalls = 0,
- replacerThis;
- return {
- replacer: function(key, value) {
- lastKey = key;
- lastValue = value;
- numCalls++;
- replacerThis = this;
- return value;
- },
- assert: function() {
- assert.equal(numCalls, 1, "Replacer should be called exactly once for " + expectedValue);
- assert.equal(lastKey, "");
- assert.deepEqual(replacerThis, {"":expectedValue});
- var expectedValueToJson = expectedValue;
- if (expectedValue && expectedValue['toJSON']) {
- expectedValueToJson = expectedValue.toJSON();
- }
- assert.equal(lastValue, expectedValueToJson);
- }
- }
- }
- }
- for (var i=0; i<simpleCases.length; i++) {
- assertStringify(simpleCases[i], replacerTestFactory(simpleCases[i]));
- }
- };
- exports.stringify.replacer.function.complexObject = function test() {
- var obj = {
- "": "emptyPropertyName",
- one: 'string',
- two: 123,
- three: ['array1', 'array2'],
- four: {nested_one:'anotherString'},
- five: new Date(),
- six: Date.now(),
- seven: null,
- eight: true,
- nine: false,
- ten: [NaN, Infinity, -Infinity],
- eleven: function() {}
- };
- var expectedKeys = [
- '', // top level object
- '', // First key
- 'one',
- 'two',
- 'three', 0, 1, // array keys
- 'four', 'nested_one', // nested object keys
- 'five',
- 'six',
- 'seven',
- 'eight',
- 'nine',
- 'ten', 0, 1, 2, // array keys
- 'eleven'
- ];
- var expectedHolders = [
- {"": obj},
- obj,
- obj,
- obj,
- obj, obj.three, obj.three,
- obj, obj.four,
- obj,
- obj,
- obj,
- obj,
- obj,
- obj, obj.ten, obj.ten, obj.ten,
- obj
- ];
- var ReplacerTest = function() {
- var seenKeys = [];
- var seenHolders = [];
- return {
- replacer: function(key, value) {
- seenKeys.push(key);
- seenHolders.push(this);
- if (typeof(value) == "object") {
- return value;
- }
- return 'replaced ' + (value ? value.toString() : '');
- },
- assert: function() {
- assert.deepEqual(seenKeys, expectedKeys);
- assert.deepEqual(seenHolders, expectedHolders);
- }
- }
- };
- assertStringify(obj, ReplacerTest);
- };
- exports.stringify.replacer.function.replacingWithUndefined = function test() {
- var obj = { shouldSurvive: 'one', shouldBeRemoved: 'two' };
- var ReplacerTest = function() {
- return {
- replacer: function(key, value) {
- if (key === 'shouldBeRemoved') {
- return undefined;
- } else {
- return value;
- }
- },
- assert: function() { /* no-op */ }
- }
- };
- assertStringify(obj, ReplacerTest);
- };
- exports.stringify.replacer.function.replacingArrayValueWithUndefined = function test() {
- var obj = ['should survive', 'should be removed'];
- var ReplacerTest = function() {
- return {
- replacer: function(key, value) {
- if (value === 'should be removed') {
- return undefined;
- } else {
- return value;
- }
- },
- assert: function() { /* no-op */ }
- }
- };
- assertStringify(obj, ReplacerTest);
- };
- exports.stringify.replacer.array = {};
- exports.stringify.replacer.array.simple = function test() {
- var ReplacerTest = function() {
- return {
- replacer: [],
- assert: function() { /* no-op */ }
- }
- };
- for (var i=0; i<simpleCases.length; i++) {
- assertStringify(simpleCases[i], ReplacerTest);
- }
- };
- exports.stringify.replacer.array.emptyStringProperty = function test() {
- var obj = {'': 'keep', 'one': 'remove'};
- var ReplacerTest = function() {
- return {
- replacer: [''],
- assert: function() {/* no-op */}
- }
- };
- assertStringify(obj, ReplacerTest);
- };
- exports.stringify.replacer.array.complexObject = function test() {
- var obj = {
- "": "emptyPropertyName",
- one: 'string',
- one_remove: 'string',
- two: 123,
- two_remove: 123,
- three: ['array1', 'array2'],
- three_remove: ['array1', 'array2'],
- four: {nested_one:'anotherString', nested_one_remove: 'anotherString'},
- four_remove: {nested_one:'anotherString', nested_one_remove: 'anotherString'},
- five: new Date(),
- five_remove: new Date(),
- six: Date.now(),
- six_remove: Date.now(),
- seven: null,
- seven_remove: null,
- eight: true,
- eight_remove: true,
- nine: false,
- nine_remove: false,
- ten: [NaN, Infinity, -Infinity],
- ten_remove: [NaN, Infinity, -Infinity],
- eleven: function() {},
- eleven_remove: function() {}
- };
- var ReplacerTest = function() {
- return {
- replacer: [
- 'one', 'two', 'three', 'four', 'nested_one', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 0
- ],
- assert: function() {/* no-op */}
- }
- };
- assertStringify(obj, ReplacerTest);
- };
- exports.stringify.toJSON = function test() {
- var customToJSONObject = {
- name: 'customToJSONObject',
- toJSON: function() {
- return 'custom-to-json-object-serialization';
- }
- };
- assertStringify(customToJSONObject);
- var customToJSONPrimitive = "Some string";
- customToJSONPrimitive.toJSON = function() {
- return 'custom-to-json-string-serialization';
- };
- assertStringify(customToJSONPrimitive);
- var object = {
- customToJSONObject: customToJSONObject
- };
- assertStringify(object);
- // Returning an object with a toJSON function does *NOT* have that toJSON function called: it is omitted
- var nested = {
- name: 'nested',
- toJSON: function() {
- return customToJSONObject;
- }
- };
- assertStringify(nested);
- var count = 0;
- function createObjectSerialisingTo(value) {
- count++;
- return {
- name: 'obj-' + count,
- toJSON: function() {
- return value;
- }
- };
- }
- assertStringify(createObjectSerialisingTo(null));
- assertStringify(createObjectSerialisingTo(undefined));
- assertStringify(createObjectSerialisingTo([]));
- assertStringify(createObjectSerialisingTo({}));
- assertStringify(createObjectSerialisingTo(12345));
- assertStringify(createObjectSerialisingTo(true));
- assertStringify(createObjectSerialisingTo(new Date()));
- assertStringify(createObjectSerialisingTo(function(){}));
- };
- function stringifyJSON5(obj, replacer, space) {
- var start, res, end;
- try {
- start = new Date();
- res = JSON5.stringify(obj, replacer, space);
- end = new Date();
- } catch (e) {
- res = e.message;
- end = new Date();
- }
- if (DEBUG) {
- console.log('JSON5.stringify time: ' + (end-start));
- console.log(res);
- }
- return res;
- }
- function stringifyJSON(obj, replacer, space) {
- var start, res, end;
-
- try {
- start = new Date();
- res = JSON.stringify(obj, replacer, space);
- end = new Date();
-
- // now remove all quotes from keys where appropriate
- // first recursively find all key names
- var keys = [];
- function findKeys(key, innerObj) {
- if (innerObj && innerObj.toJSON && typeof innerObj.toJSON === "function") {
- innerObj = innerObj.toJSON();
- }
- if (replacer) {
- if (typeof replacer === 'function') {
- innerObj = replacer(key, innerObj);
- } else if (key !== '' && replacer.indexOf(key) < 0) {
- return;
- }
- }
- if (JSON5.isWord(key) &&
- typeof innerObj !== 'function' &&
- typeof innerObj !== 'undefined') {
- keys.push(key);
- }
- if (typeof innerObj === 'object') {
- if (Array.isArray(innerObj)) {
- for (var i = 0; i < innerObj.length; i++) {
- findKeys(i, innerObj[i]);
- }
- } else if (innerObj !== null) {
- for (var prop in innerObj) {
- if (innerObj.hasOwnProperty(prop)) {
- findKeys(prop, innerObj[prop]);
- }
- }
- }
- }
- }
- findKeys('', obj);
- // now replace each key in the result
- var last = 0;
- for (var i = 0; i < keys.length; i++) {
-
- // not perfect since we can match on parts of the previous value that
- // matches the key, but we can design our test around that.
- last = res.indexOf('"' + keys[i] + '"', last);
- if (last === -1) {
- // problem with test framework
- console.log("Couldn't find: " + keys[i]);
- throw new Error("Couldn't find: " + keys[i]);
- }
- res = res.substring(0, last) +
- res.substring(last+1, last + keys[i].length+1) +
- res.substring(last + keys[i].length + 2, res.length);
- last += keys[i].length;
- }
- } catch (e) {
- res = e.message;
- end = new Date();
- }
- if (DEBUG) {
- console.log('JSON.stringify time: ' + (end-start));
- }
- return res;
- }
- function assertStringify(obj, replacerTestConstructor, expectError) {
- if (!replacerTestConstructor) {
- replacerTestConstructor = function(){
- return {replacer: null, assert: function(){}};
- };
- }
- var testStringsEqual = function(obj, indent) {
- var j5ReplacerTest = replacerTestConstructor();
- var jReplacerTest = replacerTestConstructor();
- var j5, j;
- j5 = stringifyJSON5(obj, j5ReplacerTest.replacer, indent);
- j = stringifyJSON(obj, jReplacerTest.replacer, indent);
- assert.equal(j5, j);
- j5ReplacerTest.assert();
- };
- var indents = [
- undefined,
- " ",
- " ",
- " ",
- "\t",
- "this is an odd indent",
- 5,
- 20,
- '\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'
- ];
- for (var i=0; i<indents.length; i++) {
- testStringsEqual(obj, indents[i]);
- }
- if (!expectError) {
- // no point in round tripping if there is an error
- var origStr = JSON5.stringify(obj), roundTripStr;
- if (origStr !== "undefined" && typeof origStr !== "undefined") {
- try {
- roundTripStr = JSON5.stringify(JSON5.parse(origStr));
- } catch (e) {
- console.log(e);
- console.log(origStr);
- throw e;
- }
- assert.equal(origStr, roundTripStr);
- }
- }
- }
|