// json5.js 
 | 
// Modern JSON. See README.md for details. 
 | 
// 
 | 
// This file is based directly off of Douglas Crockford's json_parse.js: 
 | 
// https://github.com/douglascrockford/JSON-js/blob/master/json_parse.js 
 | 
  
 | 
var JSON5 = (typeof exports === 'object' ? exports : {}); 
 | 
  
 | 
JSON5.parse = (function () { 
 | 
    "use strict"; 
 | 
  
 | 
// This is a function that can parse a JSON5 text, producing a JavaScript 
 | 
// data structure. It is a simple, recursive descent parser. It does not use 
 | 
// eval or regular expressions, so it can be used as a model for implementing 
 | 
// a JSON5 parser in other languages. 
 | 
  
 | 
// We are defining the function inside of another function to avoid creating 
 | 
// global variables. 
 | 
  
 | 
    var at,           // The index of the current character 
 | 
        lineNumber,   // The current line number 
 | 
        columnNumber, // The current column number 
 | 
        ch,           // The current character 
 | 
        escapee = { 
 | 
            "'":  "'", 
 | 
            '"':  '"', 
 | 
            '\\': '\\', 
 | 
            '/':  '/', 
 | 
            '\n': '',       // Replace escaped newlines in strings w/ empty string 
 | 
            b:    '\b', 
 | 
            f:    '\f', 
 | 
            n:    '\n', 
 | 
            r:    '\r', 
 | 
            t:    '\t' 
 | 
        }, 
 | 
        ws = [ 
 | 
            ' ', 
 | 
            '\t', 
 | 
            '\r', 
 | 
            '\n', 
 | 
            '\v', 
 | 
            '\f', 
 | 
            '\xA0', 
 | 
            '\uFEFF' 
 | 
        ], 
 | 
        text, 
 | 
  
 | 
        renderChar = function (chr) { 
 | 
            return chr === '' ? 'EOF' : "'" + chr + "'"; 
 | 
        }, 
 | 
  
 | 
        error = function (m) { 
 | 
  
 | 
// Call error when something is wrong. 
 | 
  
 | 
            var error = new SyntaxError(); 
 | 
            // beginning of message suffix to agree with that provided by Gecko - see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse 
 | 
            error.message = m + " at line " + lineNumber + " column " + columnNumber + " of the JSON5 data. Still to read: " + JSON.stringify(text.substring(at - 1, at + 19)); 
 | 
            error.at = at; 
 | 
            // These two property names have been chosen to agree with the ones in Gecko, the only popular 
 | 
            // environment which seems to supply this info on JSON.parse 
 | 
            error.lineNumber = lineNumber; 
 | 
            error.columnNumber = columnNumber; 
 | 
            throw error; 
 | 
        }, 
 | 
  
 | 
        next = function (c) { 
 | 
  
 | 
// If a c parameter is provided, verify that it matches the current character. 
 | 
  
 | 
            if (c && c !== ch) { 
 | 
                error("Expected " + renderChar(c) + " instead of " + renderChar(ch)); 
 | 
            } 
 | 
  
 | 
// Get the next character. When there are no more characters, 
 | 
// return the empty string. 
 | 
  
 | 
            ch = text.charAt(at); 
 | 
            at++; 
 | 
            columnNumber++; 
 | 
            if (ch === '\n' || ch === '\r' && peek() !== '\n') { 
 | 
                lineNumber++; 
 | 
                columnNumber = 0; 
 | 
            } 
 | 
            return ch; 
 | 
        }, 
 | 
  
 | 
        peek = function () { 
 | 
  
 | 
// Get the next character without consuming it or 
 | 
// assigning it to the ch varaible. 
 | 
  
 | 
            return text.charAt(at); 
 | 
        }, 
 | 
  
 | 
        identifier = function () { 
 | 
  
 | 
// Parse an identifier. Normally, reserved words are disallowed here, but we 
 | 
// only use this for unquoted object keys, where reserved words are allowed, 
 | 
// so we don't check for those here. References: 
 | 
// - http://es5.github.com/#x7.6 
 | 
// - https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Core_Language_Features#Variables 
 | 
// - http://docstore.mik.ua/orelly/webprog/jscript/ch02_07.htm 
 | 
// TODO Identifiers can have Unicode "letters" in them; add support for those. 
 | 
  
 | 
            var key = ch; 
 | 
  
 | 
            // Identifiers must start with a letter, _ or $. 
 | 
            if ((ch !== '_' && ch !== '$') && 
 | 
                    (ch < 'a' || ch > 'z') && 
 | 
                    (ch < 'A' || ch > 'Z')) { 
 | 
                error("Bad identifier as unquoted key"); 
 | 
            } 
 | 
  
 | 
            // Subsequent characters can contain digits. 
 | 
            while (next() && ( 
 | 
                    ch === '_' || ch === '$' || 
 | 
                    (ch >= 'a' && ch <= 'z') || 
 | 
                    (ch >= 'A' && ch <= 'Z') || 
 | 
                    (ch >= '0' && ch <= '9'))) { 
 | 
                key += ch; 
 | 
            } 
 | 
  
 | 
            return key; 
 | 
        }, 
 | 
  
 | 
        number = function () { 
 | 
  
 | 
// Parse a number value. 
 | 
  
 | 
            var number, 
 | 
                sign = '', 
 | 
                string = '', 
 | 
                base = 10; 
 | 
  
 | 
            if (ch === '-' || ch === '+') { 
 | 
                sign = ch; 
 | 
                next(ch); 
 | 
            } 
 | 
  
 | 
            // support for Infinity (could tweak to allow other words): 
 | 
            if (ch === 'I') { 
 | 
                number = word(); 
 | 
                if (typeof number !== 'number' || isNaN(number)) { 
 | 
                    error('Unexpected word for number'); 
 | 
                } 
 | 
                return (sign === '-') ? -number : number; 
 | 
            } 
 | 
  
 | 
            // support for NaN 
 | 
            if (ch === 'N' ) { 
 | 
              number = word(); 
 | 
              if (!isNaN(number)) { 
 | 
                error('expected word to be NaN'); 
 | 
              } 
 | 
              // ignore sign as -NaN also is NaN 
 | 
              return number; 
 | 
            } 
 | 
  
 | 
            if (ch === '0') { 
 | 
                string += ch; 
 | 
                next(); 
 | 
                if (ch === 'x' || ch === 'X') { 
 | 
                    string += ch; 
 | 
                    next(); 
 | 
                    base = 16; 
 | 
                } else if (ch >= '0' && ch <= '9') { 
 | 
                    error('Octal literal'); 
 | 
                } 
 | 
            } 
 | 
  
 | 
            switch (base) { 
 | 
            case 10: 
 | 
                while (ch >= '0' && ch <= '9' ) { 
 | 
                    string += ch; 
 | 
                    next(); 
 | 
                } 
 | 
                if (ch === '.') { 
 | 
                    string += '.'; 
 | 
                    while (next() && ch >= '0' && ch <= '9') { 
 | 
                        string += ch; 
 | 
                    } 
 | 
                } 
 | 
                if (ch === 'e' || ch === 'E') { 
 | 
                    string += ch; 
 | 
                    next(); 
 | 
                    if (ch === '-' || ch === '+') { 
 | 
                        string += ch; 
 | 
                        next(); 
 | 
                    } 
 | 
                    while (ch >= '0' && ch <= '9') { 
 | 
                        string += ch; 
 | 
                        next(); 
 | 
                    } 
 | 
                } 
 | 
                break; 
 | 
            case 16: 
 | 
                while (ch >= '0' && ch <= '9' || ch >= 'A' && ch <= 'F' || ch >= 'a' && ch <= 'f') { 
 | 
                    string += ch; 
 | 
                    next(); 
 | 
                } 
 | 
                break; 
 | 
            } 
 | 
  
 | 
            if(sign === '-') { 
 | 
                number = -string; 
 | 
            } else { 
 | 
                number = +string; 
 | 
            } 
 | 
  
 | 
            if (!isFinite(number)) { 
 | 
                error("Bad number"); 
 | 
            } else { 
 | 
                return number; 
 | 
            } 
 | 
        }, 
 | 
  
 | 
        string = function () { 
 | 
  
 | 
// Parse a string value. 
 | 
  
 | 
            var hex, 
 | 
                i, 
 | 
                string = '', 
 | 
                delim,      // double quote or single quote 
 | 
                uffff; 
 | 
  
 | 
// When parsing for string values, we must look for ' or " and \ characters. 
 | 
  
 | 
            if (ch === '"' || ch === "'") { 
 | 
                delim = ch; 
 | 
                while (next()) { 
 | 
                    if (ch === delim) { 
 | 
                        next(); 
 | 
                        return string; 
 | 
                    } else if (ch === '\\') { 
 | 
                        next(); 
 | 
                        if (ch === 'u') { 
 | 
                            uffff = 0; 
 | 
                            for (i = 0; i < 4; i += 1) { 
 | 
                                hex = parseInt(next(), 16); 
 | 
                                if (!isFinite(hex)) { 
 | 
                                    break; 
 | 
                                } 
 | 
                                uffff = uffff * 16 + hex; 
 | 
                            } 
 | 
                            string += String.fromCharCode(uffff); 
 | 
                        } else if (ch === '\r') { 
 | 
                            if (peek() === '\n') { 
 | 
                                next(); 
 | 
                            } 
 | 
                        } else if (typeof escapee[ch] === 'string') { 
 | 
                            string += escapee[ch]; 
 | 
                        } else { 
 | 
                            break; 
 | 
                        } 
 | 
                    } else if (ch === '\n') { 
 | 
                        // unescaped newlines are invalid; see: 
 | 
                        // https://github.com/aseemk/json5/issues/24 
 | 
                        // TODO this feels special-cased; are there other 
 | 
                        // invalid unescaped chars? 
 | 
                        break; 
 | 
                    } else { 
 | 
                        string += ch; 
 | 
                    } 
 | 
                } 
 | 
            } 
 | 
            error("Bad string"); 
 | 
        }, 
 | 
  
 | 
        inlineComment = function () { 
 | 
  
 | 
// Skip an inline comment, assuming this is one. The current character should 
 | 
// be the second / character in the // pair that begins this inline comment. 
 | 
// To finish the inline comment, we look for a newline or the end of the text. 
 | 
  
 | 
            if (ch !== '/') { 
 | 
                error("Not an inline comment"); 
 | 
            } 
 | 
  
 | 
            do { 
 | 
                next(); 
 | 
                if (ch === '\n' || ch === '\r') { 
 | 
                    next(); 
 | 
                    return; 
 | 
                } 
 | 
            } while (ch); 
 | 
        }, 
 | 
  
 | 
        blockComment = function () { 
 | 
  
 | 
// Skip a block comment, assuming this is one. The current character should be 
 | 
// the * character in the /* pair that begins this block comment. 
 | 
// To finish the block comment, we look for an ending */ pair of characters, 
 | 
// but we also watch for the end of text before the comment is terminated. 
 | 
  
 | 
            if (ch !== '*') { 
 | 
                error("Not a block comment"); 
 | 
            } 
 | 
  
 | 
            do { 
 | 
                next(); 
 | 
                while (ch === '*') { 
 | 
                    next('*'); 
 | 
                    if (ch === '/') { 
 | 
                        next('/'); 
 | 
                        return; 
 | 
                    } 
 | 
                } 
 | 
            } while (ch); 
 | 
  
 | 
            error("Unterminated block comment"); 
 | 
        }, 
 | 
  
 | 
        comment = function () { 
 | 
  
 | 
// Skip a comment, whether inline or block-level, assuming this is one. 
 | 
// Comments always begin with a / character. 
 | 
  
 | 
            if (ch !== '/') { 
 | 
                error("Not a comment"); 
 | 
            } 
 | 
  
 | 
            next('/'); 
 | 
  
 | 
            if (ch === '/') { 
 | 
                inlineComment(); 
 | 
            } else if (ch === '*') { 
 | 
                blockComment(); 
 | 
            } else { 
 | 
                error("Unrecognized comment"); 
 | 
            } 
 | 
        }, 
 | 
  
 | 
        white = function () { 
 | 
  
 | 
// Skip whitespace and comments. 
 | 
// Note that we're detecting comments by only a single / character. 
 | 
// This works since regular expressions are not valid JSON(5), but this will 
 | 
// break if there are other valid values that begin with a / character! 
 | 
  
 | 
            while (ch) { 
 | 
                if (ch === '/') { 
 | 
                    comment(); 
 | 
                } else if (ws.indexOf(ch) >= 0) { 
 | 
                    next(); 
 | 
                } else { 
 | 
                    return; 
 | 
                } 
 | 
            } 
 | 
        }, 
 | 
  
 | 
        word = function () { 
 | 
  
 | 
// true, false, or null. 
 | 
  
 | 
            switch (ch) { 
 | 
            case 't': 
 | 
                next('t'); 
 | 
                next('r'); 
 | 
                next('u'); 
 | 
                next('e'); 
 | 
                return true; 
 | 
            case 'f': 
 | 
                next('f'); 
 | 
                next('a'); 
 | 
                next('l'); 
 | 
                next('s'); 
 | 
                next('e'); 
 | 
                return false; 
 | 
            case 'n': 
 | 
                next('n'); 
 | 
                next('u'); 
 | 
                next('l'); 
 | 
                next('l'); 
 | 
                return null; 
 | 
            case 'I': 
 | 
                next('I'); 
 | 
                next('n'); 
 | 
                next('f'); 
 | 
                next('i'); 
 | 
                next('n'); 
 | 
                next('i'); 
 | 
                next('t'); 
 | 
                next('y'); 
 | 
                return Infinity; 
 | 
            case 'N': 
 | 
              next( 'N' ); 
 | 
              next( 'a' ); 
 | 
              next( 'N' ); 
 | 
              return NaN; 
 | 
            } 
 | 
            error("Unexpected " + renderChar(ch)); 
 | 
        }, 
 | 
  
 | 
        value,  // Place holder for the value function. 
 | 
  
 | 
        array = function () { 
 | 
  
 | 
// Parse an array value. 
 | 
  
 | 
            var array = []; 
 | 
  
 | 
            if (ch === '[') { 
 | 
                next('['); 
 | 
                white(); 
 | 
                while (ch) { 
 | 
                    if (ch === ']') { 
 | 
                        next(']'); 
 | 
                        return array;   // Potentially empty array 
 | 
                    } 
 | 
                    // ES5 allows omitting elements in arrays, e.g. [,] and 
 | 
                    // [,null]. We don't allow this in JSON5. 
 | 
                    if (ch === ',') { 
 | 
                        error("Missing array element"); 
 | 
                    } else { 
 | 
                        array.push(value()); 
 | 
                    } 
 | 
                    white(); 
 | 
                    // If there's no comma after this value, this needs to 
 | 
                    // be the end of the array. 
 | 
                    if (ch !== ',') { 
 | 
                        next(']'); 
 | 
                        return array; 
 | 
                    } 
 | 
                    next(','); 
 | 
                    white(); 
 | 
                } 
 | 
            } 
 | 
            error("Bad array"); 
 | 
        }, 
 | 
  
 | 
        object = function () { 
 | 
  
 | 
// Parse an object value. 
 | 
  
 | 
            var key, 
 | 
                object = {}; 
 | 
  
 | 
            if (ch === '{') { 
 | 
                next('{'); 
 | 
                white(); 
 | 
                while (ch) { 
 | 
                    if (ch === '}') { 
 | 
                        next('}'); 
 | 
                        return object;   // Potentially empty object 
 | 
                    } 
 | 
  
 | 
                    // Keys can be unquoted. If they are, they need to be 
 | 
                    // valid JS identifiers. 
 | 
                    if (ch === '"' || ch === "'") { 
 | 
                        key = string(); 
 | 
                    } else { 
 | 
                        key = identifier(); 
 | 
                    } 
 | 
  
 | 
                    white(); 
 | 
                    next(':'); 
 | 
                    object[key] = value(); 
 | 
                    white(); 
 | 
                    // If there's no comma after this pair, this needs to be 
 | 
                    // the end of the object. 
 | 
                    if (ch !== ',') { 
 | 
                        next('}'); 
 | 
                        return object; 
 | 
                    } 
 | 
                    next(','); 
 | 
                    white(); 
 | 
                } 
 | 
            } 
 | 
            error("Bad object"); 
 | 
        }; 
 | 
  
 | 
    value = function () { 
 | 
  
 | 
// Parse a JSON value. It could be an object, an array, a string, a number, 
 | 
// or a word. 
 | 
  
 | 
        white(); 
 | 
        switch (ch) { 
 | 
        case '{': 
 | 
            return object(); 
 | 
        case '[': 
 | 
            return array(); 
 | 
        case '"': 
 | 
        case "'": 
 | 
            return string(); 
 | 
        case '-': 
 | 
        case '+': 
 | 
        case '.': 
 | 
            return number(); 
 | 
        default: 
 | 
            return ch >= '0' && ch <= '9' ? number() : word(); 
 | 
        } 
 | 
    }; 
 | 
  
 | 
// Return the json_parse function. It will have access to all of the above 
 | 
// functions and variables. 
 | 
  
 | 
    return function (source, reviver) { 
 | 
        var result; 
 | 
  
 | 
        text = String(source); 
 | 
        at = 0; 
 | 
        lineNumber = 1; 
 | 
        columnNumber = 1; 
 | 
        ch = ' '; 
 | 
        result = value(); 
 | 
        white(); 
 | 
        if (ch) { 
 | 
            error("Syntax error"); 
 | 
        } 
 | 
  
 | 
// If there is a reviver function, we recursively walk the new structure, 
 | 
// passing each name/value pair to the reviver function for possible 
 | 
// transformation, starting with a temporary root object that holds the result 
 | 
// in an empty key. If there is not a reviver function, we simply return the 
 | 
// result. 
 | 
  
 | 
        return typeof reviver === 'function' ? (function walk(holder, key) { 
 | 
            var k, v, value = holder[key]; 
 | 
            if (value && typeof value === 'object') { 
 | 
                for (k in value) { 
 | 
                    if (Object.prototype.hasOwnProperty.call(value, k)) { 
 | 
                        v = walk(value, k); 
 | 
                        if (v !== undefined) { 
 | 
                            value[k] = v; 
 | 
                        } else { 
 | 
                            delete value[k]; 
 | 
                        } 
 | 
                    } 
 | 
                } 
 | 
            } 
 | 
            return reviver.call(holder, key, value); 
 | 
        }({'': result}, '')) : result; 
 | 
    }; 
 | 
}()); 
 | 
  
 | 
// JSON5 stringify will not quote keys where appropriate 
 | 
JSON5.stringify = function (obj, replacer, space) { 
 | 
    if (replacer && (typeof(replacer) !== "function" && !isArray(replacer))) { 
 | 
        throw new Error('Replacer must be a function or an array'); 
 | 
    } 
 | 
    var getReplacedValueOrUndefined = function(holder, key, isTopLevel) { 
 | 
        var value = holder[key]; 
 | 
  
 | 
        // Replace the value with its toJSON value first, if possible 
 | 
        if (value && value.toJSON && typeof value.toJSON === "function") { 
 | 
            value = value.toJSON(); 
 | 
        } 
 | 
  
 | 
        // If the user-supplied replacer if a function, call it. If it's an array, check objects' string keys for 
 | 
        // presence in the array (removing the key/value pair from the resulting JSON if the key is missing). 
 | 
        if (typeof(replacer) === "function") { 
 | 
            return replacer.call(holder, key, value); 
 | 
        } else if(replacer) { 
 | 
            if (isTopLevel || isArray(holder) || replacer.indexOf(key) >= 0) { 
 | 
                return value; 
 | 
            } else { 
 | 
                return undefined; 
 | 
            } 
 | 
        } else { 
 | 
            return value; 
 | 
        } 
 | 
    }; 
 | 
  
 | 
    function isWordChar(c) { 
 | 
        return (c >= 'a' && c <= 'z') || 
 | 
            (c >= 'A' && c <= 'Z') || 
 | 
            (c >= '0' && c <= '9') || 
 | 
            c === '_' || c === '$'; 
 | 
    } 
 | 
  
 | 
    function isWordStart(c) { 
 | 
        return (c >= 'a' && c <= 'z') || 
 | 
            (c >= 'A' && c <= 'Z') || 
 | 
            c === '_' || c === '$'; 
 | 
    } 
 | 
  
 | 
    function isWord(key) { 
 | 
        if (typeof key !== 'string') { 
 | 
            return false; 
 | 
        } 
 | 
        if (!isWordStart(key[0])) { 
 | 
            return false; 
 | 
        } 
 | 
        var i = 1, length = key.length; 
 | 
        while (i < length) { 
 | 
            if (!isWordChar(key[i])) { 
 | 
                return false; 
 | 
            } 
 | 
            i++; 
 | 
        } 
 | 
        return true; 
 | 
    } 
 | 
  
 | 
    // export for use in tests 
 | 
    JSON5.isWord = isWord; 
 | 
  
 | 
    // polyfills 
 | 
    function isArray(obj) { 
 | 
        if (Array.isArray) { 
 | 
            return Array.isArray(obj); 
 | 
        } else { 
 | 
            return Object.prototype.toString.call(obj) === '[object Array]'; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    function isDate(obj) { 
 | 
        return Object.prototype.toString.call(obj) === '[object Date]'; 
 | 
    } 
 | 
  
 | 
    var objStack = []; 
 | 
    function checkForCircular(obj) { 
 | 
        for (var i = 0; i < objStack.length; i++) { 
 | 
            if (objStack[i] === obj) { 
 | 
                throw new TypeError("Converting circular structure to JSON"); 
 | 
            } 
 | 
        } 
 | 
    } 
 | 
  
 | 
    function makeIndent(str, num, noNewLine) { 
 | 
        if (!str) { 
 | 
            return ""; 
 | 
        } 
 | 
        // indentation no more than 10 chars 
 | 
        if (str.length > 10) { 
 | 
            str = str.substring(0, 10); 
 | 
        } 
 | 
  
 | 
        var indent = noNewLine ? "" : "\n"; 
 | 
        for (var i = 0; i < num; i++) { 
 | 
            indent += str; 
 | 
        } 
 | 
  
 | 
        return indent; 
 | 
    } 
 | 
  
 | 
    var indentStr; 
 | 
    if (space) { 
 | 
        if (typeof space === "string") { 
 | 
            indentStr = space; 
 | 
        } else if (typeof space === "number" && space >= 0) { 
 | 
            indentStr = makeIndent(" ", space, true); 
 | 
        } else { 
 | 
            // ignore space parameter 
 | 
        } 
 | 
    } 
 | 
  
 | 
    // Copied from Crokford's implementation of JSON 
 | 
    // See https://github.com/douglascrockford/JSON-js/blob/e39db4b7e6249f04a195e7dd0840e610cc9e941e/json2.js#L195 
 | 
    // Begin 
 | 
    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 
 | 
        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 
 | 
        meta = { // table of character substitutions 
 | 
        '\b': '\\b', 
 | 
        '\t': '\\t', 
 | 
        '\n': '\\n', 
 | 
        '\f': '\\f', 
 | 
        '\r': '\\r', 
 | 
        '"' : '\\"', 
 | 
        '\\': '\\\\' 
 | 
    }; 
 | 
    function escapeString(string) { 
 | 
  
 | 
// If the string contains no control characters, no quote characters, and no 
 | 
// backslash characters, then we can safely slap some quotes around it. 
 | 
// Otherwise we must also replace the offending characters with safe escape 
 | 
// sequences. 
 | 
        escapable.lastIndex = 0; 
 | 
        return escapable.test(string) ? '"' + string.replace(escapable, function (a) { 
 | 
            var c = meta[a]; 
 | 
            return typeof c === 'string' ? 
 | 
                c : 
 | 
                '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 
 | 
        }) + '"' : '"' + string + '"'; 
 | 
    } 
 | 
    // End 
 | 
  
 | 
    function internalStringify(holder, key, isTopLevel) { 
 | 
        var buffer, res; 
 | 
  
 | 
        // Replace the value, if necessary 
 | 
        var obj_part = getReplacedValueOrUndefined(holder, key, isTopLevel); 
 | 
  
 | 
        if (obj_part && !isDate(obj_part)) { 
 | 
            // unbox objects 
 | 
            // don't unbox dates, since will turn it into number 
 | 
            obj_part = obj_part.valueOf(); 
 | 
        } 
 | 
        switch(typeof obj_part) { 
 | 
            case "boolean": 
 | 
                return obj_part.toString(); 
 | 
  
 | 
            case "number": 
 | 
                if (isNaN(obj_part) || !isFinite(obj_part)) { 
 | 
                    return "null"; 
 | 
                } 
 | 
                return obj_part.toString(); 
 | 
  
 | 
            case "string": 
 | 
                return escapeString(obj_part.toString()); 
 | 
  
 | 
            case "object": 
 | 
                if (obj_part === null) { 
 | 
                    return "null"; 
 | 
                } else if (isArray(obj_part)) { 
 | 
                    checkForCircular(obj_part); 
 | 
                    buffer = "["; 
 | 
                    objStack.push(obj_part); 
 | 
  
 | 
                    for (var i = 0; i < obj_part.length; i++) { 
 | 
                        res = internalStringify(obj_part, i, false); 
 | 
                        buffer += makeIndent(indentStr, objStack.length); 
 | 
                        if (res === null || typeof res === "undefined") { 
 | 
                            buffer += "null"; 
 | 
                        } else { 
 | 
                            buffer += res; 
 | 
                        } 
 | 
                        if (i < obj_part.length-1) { 
 | 
                            buffer += ","; 
 | 
                        } else if (indentStr) { 
 | 
                            buffer += "\n"; 
 | 
                        } 
 | 
                    } 
 | 
                    objStack.pop(); 
 | 
                    if (obj_part.length) { 
 | 
                        buffer += makeIndent(indentStr, objStack.length, true) 
 | 
                    } 
 | 
                    buffer += "]"; 
 | 
                } else { 
 | 
                    checkForCircular(obj_part); 
 | 
                    buffer = "{"; 
 | 
                    var nonEmpty = false; 
 | 
                    objStack.push(obj_part); 
 | 
                    for (var prop in obj_part) { 
 | 
                        if (obj_part.hasOwnProperty(prop)) { 
 | 
                            var value = internalStringify(obj_part, prop, false); 
 | 
                            isTopLevel = false; 
 | 
                            if (typeof value !== "undefined" && value !== null) { 
 | 
                                buffer += makeIndent(indentStr, objStack.length); 
 | 
                                nonEmpty = true; 
 | 
                                key = isWord(prop) ? prop : escapeString(prop); 
 | 
                                buffer += key + ":" + (indentStr ? ' ' : '') + value + ","; 
 | 
                            } 
 | 
                        } 
 | 
                    } 
 | 
                    objStack.pop(); 
 | 
                    if (nonEmpty) { 
 | 
                        buffer = buffer.substring(0, buffer.length-1) + makeIndent(indentStr, objStack.length) + "}"; 
 | 
                    } else { 
 | 
                        buffer = '{}'; 
 | 
                    } 
 | 
                } 
 | 
                return buffer; 
 | 
            default: 
 | 
                // functions and undefined should be ignored 
 | 
                return undefined; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    // special case...when undefined is used inside of 
 | 
    // a compound object/array, return null. 
 | 
    // but when top-level, return undefined 
 | 
    var topLevelHolder = {"":obj}; 
 | 
    if (obj === undefined) { 
 | 
        return getReplacedValueOrUndefined(topLevelHolder, '', true); 
 | 
    } 
 | 
    return internalStringify(topLevelHolder, '', true); 
 | 
}; 
 |