mas01mj@732: /* mas01mj@732: Copyright (c) 2008, Adobe Systems Incorporated mas01mj@732: All rights reserved. mas01mj@732: mas01mj@732: Redistribution and use in source and binary forms, with or without mas01mj@732: modification, are permitted provided that the following conditions are mas01mj@732: met: mas01mj@732: mas01mj@732: * Redistributions of source code must retain the above copyright notice, mas01mj@732: this list of conditions and the following disclaimer. mas01mj@732: mas01mj@732: * Redistributions in binary form must reproduce the above copyright mas01mj@732: notice, this list of conditions and the following disclaimer in the mas01mj@732: documentation and/or other materials provided with the distribution. mas01mj@732: mas01mj@732: * Neither the name of Adobe Systems Incorporated nor the names of its mas01mj@732: contributors may be used to endorse or promote products derived from mas01mj@732: this software without specific prior written permission. mas01mj@732: mas01mj@732: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS mas01mj@732: IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, mas01mj@732: THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR mas01mj@732: PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR mas01mj@732: CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, mas01mj@732: EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, mas01mj@732: PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR mas01mj@732: PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF mas01mj@732: LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING mas01mj@732: NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS mas01mj@732: SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. mas01mj@732: */ mas01mj@732: mas01mj@732: package com.adobe.serialization.json mas01mj@732: { mas01mj@732: mas01mj@732: public class JSONDecoder mas01mj@732: { mas01mj@732: mas01mj@732: /** mas01mj@732: * Flag indicating if the parser should be strict about the format mas01mj@732: * of the JSON string it is attempting to decode. mas01mj@732: */ mas01mj@732: private var strict:Boolean; mas01mj@732: mas01mj@732: /** The value that will get parsed from the JSON string */ mas01mj@732: private var value:*; mas01mj@732: mas01mj@732: /** The tokenizer designated to read the JSON string */ mas01mj@732: private var tokenizer:JSONTokenizer; mas01mj@732: mas01mj@732: /** The current token from the tokenizer */ mas01mj@732: private var token:JSONToken; mas01mj@732: mas01mj@732: /** mas01mj@732: * Constructs a new JSONDecoder to parse a JSON string mas01mj@732: * into a native object. mas01mj@732: * mas01mj@732: * @param s The JSON string to be converted mas01mj@732: * into a native object mas01mj@732: * @param strict Flag indicating if the JSON string needs to mas01mj@732: * strictly match the JSON standard or not. mas01mj@732: * @langversion ActionScript 3.0 mas01mj@732: * @playerversion Flash 9.0 mas01mj@732: * @tiptext mas01mj@732: */ mas01mj@732: public function JSONDecoder( s:String, strict:Boolean ) mas01mj@732: { mas01mj@732: this.strict = strict; mas01mj@732: tokenizer = new JSONTokenizer( s, strict ); mas01mj@732: mas01mj@732: nextToken(); mas01mj@732: value = parseValue(); mas01mj@732: mas01mj@732: // Make sure the input stream is empty mas01mj@732: if ( strict && nextToken() != null ) mas01mj@732: { mas01mj@732: tokenizer.parseError( "Unexpected characters left in input stream" ); mas01mj@732: } mas01mj@732: } mas01mj@732: mas01mj@732: /** mas01mj@732: * Gets the internal object that was created by parsing mas01mj@732: * the JSON string passed to the constructor. mas01mj@732: * mas01mj@732: * @return The internal object representation of the JSON mas01mj@732: * string that was passed to the constructor mas01mj@732: * @langversion ActionScript 3.0 mas01mj@732: * @playerversion Flash 9.0 mas01mj@732: * @tiptext mas01mj@732: */ mas01mj@732: public function getValue():* mas01mj@732: { mas01mj@732: return value; mas01mj@732: } mas01mj@732: mas01mj@732: /** mas01mj@732: * Returns the next token from the tokenzier reading mas01mj@732: * the JSON string mas01mj@732: */ mas01mj@732: private function nextToken():JSONToken mas01mj@732: { mas01mj@732: return token = tokenizer.getNextToken(); mas01mj@732: } mas01mj@732: mas01mj@732: /** mas01mj@732: * Attempt to parse an array. mas01mj@732: */ mas01mj@732: private function parseArray():Array mas01mj@732: { mas01mj@732: // create an array internally that we're going to attempt mas01mj@732: // to parse from the tokenizer mas01mj@732: var a:Array = new Array(); mas01mj@732: mas01mj@732: // grab the next token from the tokenizer to move mas01mj@732: // past the opening [ mas01mj@732: nextToken(); mas01mj@732: mas01mj@732: // check to see if we have an empty array mas01mj@732: if ( token.type == JSONTokenType.RIGHT_BRACKET ) mas01mj@732: { mas01mj@732: // we're done reading the array, so return it mas01mj@732: return a; mas01mj@732: } mas01mj@732: // in non-strict mode an empty array is also a comma mas01mj@732: // followed by a right bracket mas01mj@732: else if ( !strict && token.type == JSONTokenType.COMMA ) mas01mj@732: { mas01mj@732: // move past the comma mas01mj@732: nextToken(); mas01mj@732: mas01mj@732: // check to see if we're reached the end of the array mas01mj@732: if ( token.type == JSONTokenType.RIGHT_BRACKET ) mas01mj@732: { mas01mj@732: return a; mas01mj@732: } mas01mj@732: else mas01mj@732: { mas01mj@732: tokenizer.parseError( "Leading commas are not supported. Expecting ']' but found " + token.value ); mas01mj@732: } mas01mj@732: } mas01mj@732: mas01mj@732: // deal with elements of the array, and use an "infinite" mas01mj@732: // loop because we could have any amount of elements mas01mj@732: while ( true ) mas01mj@732: { mas01mj@732: // read in the value and add it to the array mas01mj@732: a.push( parseValue() ); mas01mj@732: mas01mj@732: // after the value there should be a ] or a , mas01mj@732: nextToken(); mas01mj@732: mas01mj@732: if ( token.type == JSONTokenType.RIGHT_BRACKET ) mas01mj@732: { mas01mj@732: // we're done reading the array, so return it mas01mj@732: return a; mas01mj@732: } mas01mj@732: else if ( token.type == JSONTokenType.COMMA ) mas01mj@732: { mas01mj@732: // move past the comma and read another value mas01mj@732: nextToken(); mas01mj@732: mas01mj@732: // Allow arrays to have a comma after the last element mas01mj@732: // if the decoder is not in strict mode mas01mj@732: if ( !strict ) mas01mj@732: { mas01mj@732: // Reached ",]" as the end of the array, so return it mas01mj@732: if ( token.type == JSONTokenType.RIGHT_BRACKET ) mas01mj@732: { mas01mj@732: return a; mas01mj@732: } mas01mj@732: } mas01mj@732: } mas01mj@732: else mas01mj@732: { mas01mj@732: tokenizer.parseError( "Expecting ] or , but found " + token.value ); mas01mj@732: } mas01mj@732: } mas01mj@732: return null; mas01mj@732: } mas01mj@732: mas01mj@732: /** mas01mj@732: * Attempt to parse an object. mas01mj@732: */ mas01mj@732: private function parseObject():Object mas01mj@732: { mas01mj@732: // create the object internally that we're going to mas01mj@732: // attempt to parse from the tokenizer mas01mj@732: var o:Object = new Object(); mas01mj@732: mas01mj@732: // store the string part of an object member so mas01mj@732: // that we can assign it a value in the object mas01mj@732: var key:String mas01mj@732: mas01mj@732: // grab the next token from the tokenizer mas01mj@732: nextToken(); mas01mj@732: mas01mj@732: // check to see if we have an empty object mas01mj@732: if ( token.type == JSONTokenType.RIGHT_BRACE ) mas01mj@732: { mas01mj@732: // we're done reading the object, so return it mas01mj@732: return o; mas01mj@732: } mas01mj@732: // in non-strict mode an empty object is also a comma mas01mj@732: // followed by a right bracket mas01mj@732: else if ( !strict && token.type == JSONTokenType.COMMA ) mas01mj@732: { mas01mj@732: // move past the comma mas01mj@732: nextToken(); mas01mj@732: mas01mj@732: // check to see if we're reached the end of the object mas01mj@732: if ( token.type == JSONTokenType.RIGHT_BRACE ) mas01mj@732: { mas01mj@732: return o; mas01mj@732: } mas01mj@732: else mas01mj@732: { mas01mj@732: tokenizer.parseError( "Leading commas are not supported. Expecting '}' but found " + token.value ); mas01mj@732: } mas01mj@732: } mas01mj@732: mas01mj@732: // deal with members of the object, and use an "infinite" mas01mj@732: // loop because we could have any amount of members mas01mj@732: while ( true ) mas01mj@732: { mas01mj@732: if ( token.type == JSONTokenType.STRING ) mas01mj@732: { mas01mj@732: // the string value we read is the key for the object mas01mj@732: key = String( token.value ); mas01mj@732: mas01mj@732: // move past the string to see what's next mas01mj@732: nextToken(); mas01mj@732: mas01mj@732: // after the string there should be a : mas01mj@732: if ( token.type == JSONTokenType.COLON ) mas01mj@732: { mas01mj@732: // move past the : and read/assign a value for the key mas01mj@732: nextToken(); mas01mj@732: o[key] = parseValue(); mas01mj@732: mas01mj@732: // move past the value to see what's next mas01mj@732: nextToken(); mas01mj@732: mas01mj@732: // after the value there's either a } or a , mas01mj@732: if ( token.type == JSONTokenType.RIGHT_BRACE ) mas01mj@732: { mas01mj@732: // we're done reading the object, so return it mas01mj@732: return o; mas01mj@732: } mas01mj@732: else if ( token.type == JSONTokenType.COMMA ) mas01mj@732: { mas01mj@732: // skip past the comma and read another member mas01mj@732: nextToken(); mas01mj@732: mas01mj@732: // Allow objects to have a comma after the last member mas01mj@732: // if the decoder is not in strict mode mas01mj@732: if ( !strict ) mas01mj@732: { mas01mj@732: // Reached ",}" as the end of the object, so return it mas01mj@732: if ( token.type == JSONTokenType.RIGHT_BRACE ) mas01mj@732: { mas01mj@732: return o; mas01mj@732: } mas01mj@732: } mas01mj@732: } mas01mj@732: else mas01mj@732: { mas01mj@732: tokenizer.parseError( "Expecting } or , but found " + token.value ); mas01mj@732: } mas01mj@732: } mas01mj@732: else mas01mj@732: { mas01mj@732: tokenizer.parseError( "Expecting : but found " + token.value ); mas01mj@732: } mas01mj@732: } mas01mj@732: else mas01mj@732: { mas01mj@732: tokenizer.parseError( "Expecting string but found " + token.value ); mas01mj@732: } mas01mj@732: } mas01mj@732: return null; mas01mj@732: } mas01mj@732: mas01mj@732: /** mas01mj@732: * Attempt to parse a value mas01mj@732: */ mas01mj@732: private function parseValue():Object mas01mj@732: { mas01mj@732: // Catch errors when the input stream ends abruptly mas01mj@732: if ( token == null ) mas01mj@732: { mas01mj@732: tokenizer.parseError( "Unexpected end of input" ); mas01mj@732: } mas01mj@732: mas01mj@732: switch ( token.type ) mas01mj@732: { mas01mj@732: case JSONTokenType.LEFT_BRACE: mas01mj@732: return parseObject(); mas01mj@732: mas01mj@732: case JSONTokenType.LEFT_BRACKET: mas01mj@732: return parseArray(); mas01mj@732: mas01mj@732: case JSONTokenType.STRING: mas01mj@732: case JSONTokenType.NUMBER: mas01mj@732: case JSONTokenType.TRUE: mas01mj@732: case JSONTokenType.FALSE: mas01mj@732: case JSONTokenType.NULL: mas01mj@732: return token.value; mas01mj@732: mas01mj@732: case JSONTokenType.NAN: mas01mj@732: if ( !strict ) mas01mj@732: { mas01mj@732: return token.value; mas01mj@732: } mas01mj@732: else mas01mj@732: { mas01mj@732: tokenizer.parseError( "Unexpected " + token.value ); mas01mj@732: } mas01mj@732: mas01mj@732: default: mas01mj@732: tokenizer.parseError( "Unexpected " + token.value ); mas01mj@732: mas01mj@732: } mas01mj@732: mas01mj@732: return null; mas01mj@732: } mas01mj@732: } mas01mj@732: }