﻿// This source code is free for use in the public domain.
// NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.

// http://code.google.com/p/json-sans-eval/

/**
* Parses a string of well-formed JSON text.
*
* If the input is not well-formed, then behavior is undefined, but it is
* deterministic and is guaranteed not to modify any object other than its
* return value.
*
* This does not use `eval` so is less likely to have obscure security bugs than
* json2.js.
* It is optimized for speed, so is much faster than json_parse.js.
*
* This library should be used whenever security is a concern (when JSON may
* come from an untrusted source), speed is a concern, and erroring on malformed
* JSON is *not* a concern.
*
*                      Pros                   Cons
*                    +-----------------------+-----------------------+
* json_sans_eval.js  | Fast, secure          | Not validating        |
*                    +-----------------------+-----------------------+
* json_parse.js      | Validating, secure    | Slow                  |
*                    +-----------------------+-----------------------+
* json2.js           | Fast, some validation | Potentially insecure  |
*                    +-----------------------+-----------------------+
*
* json2.js is very fast, but potentially insecure since it calls `eval` to
* parse JSON data, so an attacker might be able to supply strange JS that
* looks like JSON, but that executes arbitrary javascript.
* If you do have to use json2.js with untrusted data, make sure you keep
* your version of json2.js up to date so that you get patches as they're
* released.
*
* @param {string} json per RFC 4627
* @param {function} opt_reviver optional function that reworks JSON objects
*     post-parse per Chapter 15.12 of EcmaScript3.1.
*     If supplied, the function is called with a string key, and a value.
*     The value is the property of 'this'.  The reviver should return
*     the value to use in its place.  So if dates were serialized as
*     {@code { "type": "Date", "time": 1234 }}, then a reviver might look like
*     {@code
*     function (key, value) {
*       if (value && typeof value === 'object' && 'Date' === value.type) {
*         return new Date(value.time);
*       } else {
*         return value;
*       }
*     }}.
*     If the reviver returns {@code undefined} then the property named by key
*     will be deleted from its container.
*     {@code this} is bound to the object containing the specified property.
* @return {Object|Array}
* @author Mike Samuel <mikesamuel@gmail.com>
*/
var jsonParse = (function() {
    var number
      = '(?:-?\\b(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\\b)';
    var oneChar = '(?:[^\\0-\\x08\\x0a-\\x1f\"\\\\]'
      + '|\\\\(?:[\"/\\\\bfnrt]|u[0-9A-Fa-f]{4}))';
    var string = '(?:\"' + oneChar + '*\")';

    // Will match a value in a well-formed JSON file.
    // If the input is not well-formed, may match strangely, but not in an unsafe
    // way.
    // Since this only matches value tokens, it does not match whitespace, colons,
    // or commas.
    var jsonToken = new RegExp(
      '(?:false|true|null|[\\{\\}\\[\\]]'
      + '|' + number
      + '|' + string
      + ')', 'g');

    // Matches escape sequences in a string literal
    var escapeSequence = new RegExp('\\\\(?:([^u])|u(.{4}))', 'g');

    // Decodes escape sequences in object literals
    var escapes = {
        '"': '"',
        '/': '/',
        '\\': '\\',
        'b': '\b',
        'f': '\f',
        'n': '\n',
        'r': '\r',
        't': '\t'
    };
    function unescapeOne(_, ch, hex) {
        return ch ? escapes[ch] : String.fromCharCode(parseInt(hex, 16));
    }

    // A non-falsy value that coerces to the empty string when used as a key.
    var EMPTY_STRING = new String('');
    var SLASH = '\\';

    // Constructor to use based on an open token.
    var firstTokenCtors = { '{': Object, '[': Array };

    var hop = Object.hasOwnProperty;

    return function(json, opt_reviver) {
        // Split into tokens
        var toks = json.match(jsonToken);
        // Construct the object to return
        var result;
        var tok = toks[0];
        var topLevelPrimitive = false;
        if ('{' === tok) {
            result = {};
        } else if ('[' === tok) {
            result = [];
        } else {
            // The RFC only allows arrays or objects at the top level, but the JSON.parse
            // defined by the EcmaScript 5 draft does allow strings, booleans, numbers, and null
            // at the top level.
            result = [];
            topLevelPrimitive = true;
        }

        // If undefined, the key in an object key/value record to use for the next
        // value parsed.
        var key;
        // Loop over remaining tokens maintaining a stack of uncompleted objects and
        // arrays.
        var stack = [result];
        for (var i = 1 - topLevelPrimitive, n = toks.length; i < n; ++i) {
            tok = toks[i];

            var cont;
            switch (tok.charCodeAt(0)) {
                default:  // sign or digit
                    cont = stack[0];
                    cont[key || cont.length] = +(tok);
                    key = void 0;
                    break;
                case 0x22:  // '"'
                    tok = tok.substring(1, tok.length - 1);
                    if (tok.indexOf(SLASH) !== -1) {
                        tok = tok.replace(escapeSequence, unescapeOne);
                    }
                    cont = stack[0];
                    if (!key) {
                        if (cont instanceof Array) {
                            key = cont.length;
                        } else {
                            key = tok || EMPTY_STRING;  // Use as key for next value seen.
                            break;
                        }
                    }
                    cont[key] = tok;
                    key = void 0;
                    break;
                case 0x5b:  // '['
                    cont = stack[0];
                    stack.unshift(cont[key || cont.length] = []);
                    key = void 0;
                    break;
                case 0x5d:  // ']'
                    stack.shift();
                    break;
                case 0x66:  // 'f'
                    cont = stack[0];
                    cont[key || cont.length] = false;
                    key = void 0;
                    break;
                case 0x6e:  // 'n'
                    cont = stack[0];
                    cont[key || cont.length] = null;
                    key = void 0;
                    break;
                case 0x74:  // 't'
                    cont = stack[0];
                    cont[key || cont.length] = true;
                    key = void 0;
                    break;
                case 0x7b:  // '{'
                    cont = stack[0];
                    stack.unshift(cont[key || cont.length] = {});
                    key = void 0;
                    break;
                case 0x7d:  // '}'
                    stack.shift();
                    break;
            }
        }
        // Fail if we've got an uncompleted object.
        if (topLevelPrimitive) {
            if (stack.length !== 1) { throw new Error(); }
            result = result[0];
        } else {
            if (stack.length) { throw new Error(); }
        }

        if (opt_reviver) {
            // Based on walk as implemented in http://www.json.org/json2.js
            var walk = function(holder, key) {
                var value = holder[key];
                if (value && typeof value === 'object') {
                    var toDelete = null;
                    for (var k in value) {
                        if (hop.call(value, k) && value !== holder) {
                            // Recurse to properties first.  This has the effect of causing
                            // the reviver to be called on the object graph depth-first.

                            // Since 'this' is bound to the holder of the property, the
                            // reviver can access sibling properties of k including ones
                            // that have not yet been revived.

                            // The value returned by the reviver is used in place of the
                            // current value of property k.
                            // If it returns undefined then the property is deleted.
                            var v = walk(value, k);
                            if (v !== void 0) {
                                value[k] = v;
                            } else {
                                // Deleting properties inside the loop has vaguely defined
                                // semantics in ES3 and ES3.1.
                                if (!toDelete) { toDelete = []; }
                                toDelete.push(k);
                            }
                        }
                    }
                    if (toDelete) {
                        for (var i = toDelete.length; --i >= 0; ) {
                            delete value[toDelete[i]];
                        }
                    }
                }
                return opt_reviver.call(holder, key, value);
            };
            result = walk({ '': result }, '');
        }

        return result;
    };
})();

