#include "bu/json.h" #include "bu/staticmembuf.h" #include "bu/membuf.h" #include "bu/sio.h" #include #define next( txt ) readChar( c, sInput, "Unexpected end of stream while reading " txt "." ) Bu::Json::Json() : eType( Null ) { } Bu::Json::Json( const Bu::UtfString &sValue ) : eType( String ), uDat( sValue.get() ) { } Bu::Json::Json( const Bu::String &sValue ) : eType( String ), uDat( sValue ) { } Bu::Json::Json( const char *sValue ) : eType( String ), uDat( sValue ) { } Bu::Json::Json( double dValue ) : eType( Number ), uDat( dValue ) { } Bu::Json::Json( bool bValue ) : eType( Boolean ), uDat( bValue ) { } Bu::Json::Json( Type eType ) : eType( eType ) { switch( eType ) { case Object: uDat.pObject = new JsonHash(); break; case Array: uDat.pArray = new JsonList(); break; case String: uDat.pString = new Bu::String(); break; case Number: case Boolean: case Null: case Invalid: uDat.pObject = NULL; break; } } Bu::Json::Json( Bu::Stream &sInput ) : eType( Invalid ) { parse( sInput ); } Bu::Json::Json( char &c, Bu::Stream &sInput ) : eType( Invalid ) { parse( c, sInput ); } Bu::Json::~Json() { reset(); } Bu::Json::Type Bu::Json::getType() const { return eType; } Bu::String Bu::Json::getString() const { if( eType != String ) throw Bu::ExceptionBase( "String requested from non-string json object." ); return *uDat.pString; } double Bu::Json::getNumber() const { if( eType != Number ) throw Bu::ExceptionBase( "Number requested from non-number json object." ); return uDat.dNumber; } bool Bu::Json::getBoolean() const { if( eType != Boolean ) throw Bu::ExceptionBase( "Boolean requested from non-boolean json object." ); return uDat.bBoolean; } bool Bu::Json::isNull() const { return eType == Null; } Bu::Json *Bu::Json::operator[]( const Bu::String &sKey ) const { if( eType != Object ) throw Bu::ExceptionBase( "Object entry requested from non-object json object." ); return uDat.pObject->get( sKey ); } int Bu::Json::getSize() const { if( eType == Object ) return uDat.pObject->getSize(); else if( eType == Array ) return uDat.pArray->getSize(); else if( eType == String ) return uDat.pString->getSize(); else throw Bu::ExceptionBase( "Size requseted from json type that doesn't support it." ); } Bu::Json::iterator Bu::Json::begin() { return uDat.pArray->begin(); } Bu::Json::const_iterator Bu::Json::begin() const { return uDat.pArray->begin(); } Bu::Json::iterator Bu::Json::end() { return uDat.pArray->end(); } Bu::Json::const_iterator Bu::Json::end() const { return uDat.pArray->end(); } bool Bu::Json::has( const Bu::String &sKey ) const { return uDat.pObject->has( sKey ); } void Bu::Json::insert( const Bu::String &sKey, Bu::Json *pObj ) { uDat.pObject->insert( sKey, pObj ); } void Bu::Json::append( Bu::Json *pObj ) { uDat.pArray->append( pObj ); } void Bu::Json::parse( Bu::Stream &sInput ) { reset(); char c; next("json"); parse( c, sInput ); } void Bu::Json::parse( const Bu::String &sInput ) { Bu::StaticMemBuf mb( sInput.getStr(), sInput.getSize() ); parse( mb ); } void Bu::Json::parse( char &c, Bu::Stream &sInput ) { while( c == ' ' || c == '\t' || c == '\r' || c == '\n' ) { next( "json" ); } if( c == '"' ) { // String parseString( c, sInput ); } else if( c == '{' ) { // Object parseObject( c, sInput ); } else if( c == '[' ) { // Array parseArray( c, sInput ); } else if( c == '-' || (c >= '0' && c <= '9') ) { // Number -- apparently they can't start with a period parseNumber( c, sInput ); } else if( c == 't' || c == 'f' || c == 'n' ) { // True / false / null parseLiteral( c, sInput ); } else { throw Bu::ExceptionBase("Invalid characters in json stream."); } } void Bu::Json::reset() { switch( eType ) { case Object: for( JsonHash::iterator i = uDat.pObject->begin(); i; i++ ) { delete i.getValue(); } delete uDat.pObject; break; case Array: for( JsonList::iterator i = uDat.pArray->begin(); i; i++ ) { delete *i; } delete uDat.pArray; break; case String: delete uDat.pString; break; case Invalid: case Number: case Boolean: case Null: break; } uDat.pObject = NULL; } void Bu::Json::write( Bu::Stream &sOutput ) const { switch( eType ) { case Invalid: throw Bu::ExceptionBase("Invalid type in json"); break; case Object: { sOutput.write("{", 1 ); bool bFirst = true; for( JsonHash::iterator i = uDat.pObject->begin(); i; i++ ) { if( bFirst == true ) bFirst = false; else sOutput.write(","); writeStr( i.getKey(), sOutput ); sOutput.write(":", 1 ); (*i)->write( sOutput ); } sOutput.write("}", 1 ); } break; case Array: { sOutput.write("[", 1); bool bFirst = true; for( JsonList::iterator i = uDat.pArray->begin(); i; i++ ) { if( bFirst == true ) bFirst = false; else sOutput.write(",", 1 ); (*i)->write( sOutput ); } sOutput.write("]", 1); } break; case String: writeStr( *uDat.pString, sOutput ); break; case Number: sOutput.write(Bu::String("%1").arg( uDat.dNumber )); break; case Boolean: if( uDat.bBoolean ) sOutput.write("true", 4 ); else sOutput.write("false", 5 ); break; case Null: sOutput.write("null", 4 ); break; } } void Bu::Json::writeStable( Bu::Stream &sOutput ) const { if( eType == Object ) { sOutput.write("{", 1 ); bool bFirst = true; Bu::List lKey = uDat.pObject->getKeys(); lKey.sort(); for( Bu::List::iterator i = lKey.begin(); i; i++ ) { if( bFirst == true ) bFirst = false; else sOutput.write(","); writeStr( *i, sOutput ); sOutput.write(":", 1 ); uDat.pObject->get( *i )->write( sOutput ); } sOutput.write("}", 1 ); } else { write( sOutput ); } } Bu::String Bu::Json::toString() const { Bu::MemBuf mb; write( mb ); return mb.getString(); } Bu::String Bu::Json::toStringStable() const { Bu::MemBuf mb; writeStable( mb ); return mb.getString(); } void Bu::Json::parseString( char &c, Bu::Stream &sInput, Bu::String &sOut ) { skipWs( c, sInput ); bool bEscape = false; for(;;) { next( "string" ); if( bEscape ) { switch( c ) { case '"': case '\\': case '/': sOut += c; break; case 'b': sOut += '\b'; break; case 'f': sOut += '\f'; break; case 'n': sOut += '\n'; break; case 'r': sOut += '\r'; break; case 't': sOut += '\t'; break; case 'u': // Not implimented yet, followed by four hex diigts break; default: throw Bu::ExceptionBase( "Invalid escape sequence encountered in string." ); break; } bEscape = false; } else { if( c == '\\' ) bEscape = true; if( c == '"' ) { readChar( c, sInput ); break; } sOut += c; } } } void Bu::Json::parseString( char &c, Bu::Stream &sInput ) { eType = String; uDat.pString = new Bu::String(); parseString( c, sInput, *uDat.pString ); } void Bu::Json::parseObject( char &c, Bu::Stream &sInput ) { skipWs( c, sInput ); eType = Object; uDat.pObject = new JsonHash(); next( "object" ); for(;;) { Bu::String sKey; parseString( c, sInput, sKey ); skipWs( c, sInput ); if( c != ':' ) { throw Bu::ExceptionBase( "Invalid json, expected colon after key in object." ); } next("object"); uDat.pObject->insert( sKey, new Json( c, sInput ) ); skipWs( c, sInput ); if( c == '}' ) { readChar( c, sInput ); break; } else if( c == ',' ) next( "object" ); else throw Bu::ExceptionBase( "Invalid json, expected comma or } after value in object." ); } } void Bu::Json::parseArray( char &c, Bu::Stream &sInput ) { skipWs( c, sInput ); eType = Array; uDat.pArray = new JsonList(); next("array"); for(;;) { uDat.pArray->append( new Json( c, sInput ) ); skipWs( c, sInput ); if( c == ']' ) { readChar( c, sInput ); break; } else if( c == ',' ) { next("array"); continue; } else { throw Bu::ExceptionBase( "Invalid json, expected comma or ] after value in array." ); } } } void Bu::Json::parseNumber( char &c, Bu::Stream &sInput ) { skipWs( c, sInput ); Bu::String sBuf; if( c == '-' ) { sBuf += c; next( "number" ); } bool bIntPart = true; do { if( c >= '0' && c <= '9' ) sBuf += c; else if( c == '.' && bIntPart == true ) { bIntPart = false; sBuf += c; } else if( c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '}' || c == ']' || c == ',' ) { break; } else { throw Bu::ExceptionBase("Invalid character in number."); } } while( readChar( c, sInput ) ); eType = Number; uDat.dNumber = atof( sBuf.getStr() ); } void Bu::Json::parseLiteral( char &c, Bu::Stream &sInput ) { skipWs( c, sInput ); Bu::String s; do { if( isWs( c ) || c == ',' || c == '}' || c == ']' ) break; else s += c; } while( readChar( c, sInput ) ); if( s == "true" ) { eType = Boolean; uDat.bBoolean = true; } else if( s == "false" ) { eType = Boolean; uDat.bBoolean = false; } else if( s == "null" ) { eType = Null; uDat.pObject = NULL; } else { throw Bu::ExceptionBase("Invalid literal token found."); } } bool Bu::Json::readChar( char &c, Bu::Stream &sInput ) { if( sInput.read( &c, 1 ) == 0 ) return false; return true; } void Bu::Json::readChar( char &c, Bu::Stream &sInput, const char *sSection ) { if( sInput.read( &c, 1 ) == 0 ) { throw Bu::ExceptionBase( sSection ); } } bool Bu::Json::isWs( char c ) { return c == ' ' || c == '\t' || c == '\r' || c == '\n'; } void Bu::Json::skipWs( char &c, Bu::Stream &sInput ) { while( isWs( c ) ) { next("whitespace"); } } void Bu::Json::writeStr( const Bu::String &sStr, Bu::Stream &sOutput ) const { sOutput.write("\"", 1 ); for( Bu::String::const_iterator i = sStr.begin(); i; i++ ) { switch( *i ) { case '"': sOutput.write("\\\"", 2 ); break; case '\\': sOutput.write("\\\\", 2 ); break; case '/': sOutput.write("\\/", 2 ); break; case '\b': sOutput.write("\\b", 2 ); break; case '\f': sOutput.write("\\f", 2 ); break; case '\n': sOutput.write("\\n", 2 ); break; case '\r': sOutput.write("\\r", 2 ); break; case '\t': sOutput.write("\\t", 2 ); break; default: sOutput.write( &(*i), 1 ); } } sOutput.write("\"", 1 ); }