blob: 42aa44b053becb450399a7be25cfa7ec2d3103d6 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/**
* @fileoverview Defines a stable stringifier that handles cycles.
* @private
*/
module.exports = stableCircularStringify;
require('./es6-shim');
function stableCircularStringifyInternal(val, seen) {
if (typeof val === 'number' || typeof val === 'boolean' ||
val === undefined || val === null) {
return '' + val;
}
if (typeof val === 'string') {
return '"' + val.replace(/\"/, '\\"') + '"';
}
if (val instanceof Date) {
return val.toString();
}
var i;
if (seen.has(val)) {
var ret = seen.get(val);
if (ret.hasOwnProperty('output')) {
return ret.output;
} else {
return 'ID[' + ret.id + ']';
}
}
var seenObj = { id: seen.size };
seen.set(val, seenObj);
// TODO(alexfandrianto): UintXArray and the other TypedArray seem to be in
// flux right now, with respect to their node and browser implementations.
// TypedArray doesn't seem to exist in 'node', but it looks like it's being
// added in the browser. For now, we will check if the internal buffer is an
// ArrayBuffer to identify TypedArray in both node and browser.
// https://github.com/vanadium/issues/issues/692
if (Array.isArray(val) || val.buffer instanceof ArrayBuffer) {
var arrStr = '[';
for (var ai = 0; ai < val.length; ai++) {
if (ai > 0) {
arrStr += ',';
}
arrStr += stableCircularStringifyInternal(val[ai], seen);
}
arrStr += ']';
// Attach the str to the object in seen to short-circuit lookup.
seenObj.output = arrStr;
return arrStr;
}
// Extract val's keys and values in a consistent order.
var keys = [];
var values = [];
if (val instanceof Set || val instanceof Map) {
// We have to make sure to print maps and sets in sorted key order.
// While Set and Map have an iteration order equivalent to their insertion
// order, we still want non-matching insertion orders to have matching
// stringify output.
val.forEach(function(value, key) {
keys.push(key);
});
keys.sort();
keys.forEach(function(key) {
if (val instanceof Set) {
values.push(true); // {X:true} is our desired format for set.
} else {
values.push(val.get(key));
}
});
} else {
// Extract and sort Object keys to ensure consistent key order.
keys = Object.keys(val);
keys.sort();
keys.forEach(function(key) {
values.push(val[key]);
});
}
// Pretty print the object keys and values.
var str = '{';
for (i = 0; i < keys.length; i++) {
if (i > 0) {
str += ',';
}
str += stableCircularStringifyInternal(keys[i], seen);
str += ':';
str += stableCircularStringifyInternal(values[i], seen);
}
str += '}';
// Attach the str to the object in seen to short-circuit lookup.
seenObj.output = str;
return str;
}
/**
* Converts an object to a string in a stable manner, outputting ids for cycles.
* This is necessary because JSON stringify won't handle circular types
* properly and is not guaranteed to be stable for maps.
* TODO(bprosnitz) Make this faster.
* @private
* @param {Type} type the type object.
* @return {string} The key.
*/
function stableCircularStringify(val) {
return stableCircularStringifyInternal(val, new Map());
}