#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 ) { } 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::UtfString(); 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( Bu::UtfChar &c, Bu::Stream &sInput ) : eType( Invalid ) { parse( c, sInput ); } Bu::Json::Json( const Json &rSrc ) : eType( Invalid ) { (*this) = rSrc; } Bu::Json::~Json() { reset(); } Bu::Json::Type Bu::Json::getType() const { return eType; } Bu::UtfString 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::UtfString &sKey ) const { if( eType != Object ) throw Bu::ExceptionBase( "Object entry requested from non-object json object." ); return *uDat.pObject->get( sKey ); } Bu::Json &Bu::Json::operator[]( int iIndex ) const { if( eType != Array ) throw Bu::ExceptionBase( "Object entry requested from non-array json object." ); return *uDat.pArray->get( iIndex ); } int Bu::Json::getSize() const { if( eType == Object ) return uDat.pObject->getSize(); else if( eType == Array ) return uDat.pArray->getSize(); else throw Bu::ExceptionBase( "Size requseted from json type that doesn't support it." ); } Bu::UtfStringList Bu::Json::getKeys() const { return uDat.pObject->getKeys(); } 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 ); } Bu::Json &Bu::Json::insert( const Bu::String &sKey, Bu::Json *pObj ) { uDat.pObject->insert( sKey, pObj ); return *this; } Bu::Json &Bu::Json::insert( const Bu::String &sKey, const Bu::Json &rObj ) { uDat.pObject->insert( sKey, new Bu::Json( rObj ) ); return *this; } Bu::Json &Bu::Json::insert( const Bu::String &sKey, const Bu::String &sValue ) { uDat.pObject->insert( sKey, new Json( sValue ) ); return *this; } Bu::Json &Bu::Json::insert( const Bu::String &sKey, const char *sValue ) { uDat.pObject->insert( sKey, new Json( sValue ) ); return *this; } Bu::Json &Bu::Json::insert( const Bu::String &sKey, double dValue ) { uDat.pObject->insert( sKey, new Json( dValue ) ); return *this; } Bu::Json &Bu::Json::insert( const Bu::String &sKey, bool bValue ) { uDat.pObject->insert( sKey, new Json( bValue ) ); return *this; } Bu::Json &Bu::Json::insertObject( const Bu::String &sKey ) { Json *pOb = new Json( Object ); uDat.pObject->insert( sKey, pOb ); return *pOb; } Bu::Json &Bu::Json::insertArray( const Bu::String &sKey ) { Json *pAr = new Json( Array ); uDat.pObject->insert( sKey, pAr ); return *pAr; } Bu::Json &Bu::Json::append( Bu::Json *pObj ) { uDat.pArray->append( pObj ); return *this; } Bu::Json &Bu::Json::append( const Bu::String &sValue ) { uDat.pArray->append( new Json( sValue ) ); return *this; } Bu::Json &Bu::Json::append( const char *sValue ) { uDat.pArray->append( new Json( sValue ) ); return *this; } Bu::Json &Bu::Json::append( double dValue ) { uDat.pArray->append( new Json( dValue ) ); return *this; } Bu::Json &Bu::Json::append( bool bValue ) { uDat.pArray->append( new Json( bValue ) ); return *this; } Bu::Json &Bu::Json::appendObject() { if( eType != Array ) throw Bu::ExceptionBase("Cannot append to non-array Json Types."); Json *pOb = new Json( Object ); uDat.pArray->append( pOb ); return *pOb; } Bu::Json &Bu::Json::appendArray() { Json *pAr = new Json( Array ); uDat.pArray->append( pAr ); return *pAr; } void Bu::Json::parse( Bu::Stream &sInput ) { reset(); Bu::UtfChar 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( Bu::UtfChar &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; eType = Invalid; } 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(); } Bu::Json &Bu::Json::operator=( const Bu::Json &rSrc ) { reset(); eType = rSrc.eType; switch( eType ) { case Invalid: case Null: break; case String: uDat.pString = new Bu::UtfString( *rSrc.uDat.pString ); break; case Number: uDat.dNumber = rSrc.uDat.dNumber; break; case Boolean: uDat.bBoolean = rSrc.uDat.bBoolean; break; case Object: uDat.pObject = new JsonHash(); for( JsonHash::const_iterator i = rSrc.uDat.pObject->begin(); i; ++i ) { uDat.pObject->insert( i.getKey(), new Json( *i.getValue() ) ); } break; case Array: uDat.pArray = new JsonList(); for( JsonList::const_iterator i = rSrc.uDat.pArray->begin(); i; ++i ) { uDat.pArray->append( new Json( *(*i) ) ); } break; } return *this; } void Bu::Json::parseString( Bu::UtfChar &c, Bu::Stream &sInput, Bu::UtfString &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; else if( c == '"' ) { readChar( c, sInput ); break; } else sOut += c; } } } void Bu::Json::parseString( Bu::UtfChar &c, Bu::Stream &sInput ) { eType = String; uDat.pString = new Bu::UtfString(); parseString( c, sInput, *uDat.pString ); } void Bu::Json::parseObject( Bu::UtfChar &c, Bu::Stream &sInput ) { skipWs( c, sInput ); eType = Object; uDat.pObject = new JsonHash(); next( "object" ); skipWs( c, sInput ); // Check to see if it's an empty object. if( c == '}' ) { next("object"); return; } for(;;) { Bu::UtfString 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( Bu::UtfChar &c, Bu::Stream &sInput ) { skipWs( c, sInput ); eType = Array; uDat.pArray = new JsonList(); next("array"); // Check to see if it's an empty array. if( c == ']' ) { next("array"); return; } 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( Bu::UtfChar &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( Bu::UtfChar &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( Bu::UtfChar &c, Bu::Stream &sInput ) { if( Bu::UtfString::readPoint( sInput, c ) == 0 && sInput.isEos() ) return false; return true; } void Bu::Json::readChar( Bu::UtfChar &c, Bu::Stream &sInput, const char *sSection ) { if( Bu::UtfString::readPoint( sInput, c ) == 0 && sInput.isEos() ) { throw Bu::ExceptionBase( sSection ); } } bool Bu::Json::isWs( Bu::UtfChar c ) { return c == ' ' || c == '\t' || c == '\r' || c == '\n'; } void Bu::Json::skipWs( Bu::UtfChar &c, Bu::Stream &sInput ) { while( isWs( c ) ) { next("whitespace"); } } void Bu::Json::writeStr( const Bu::UtfString &sStr, Bu::Stream &sOutput ) const { sOutput.write("\"", 1 ); for( Bu::UtfString::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: if( *i < 32 ) sOutput.write( Bu::String("\\u%1"). arg( (uint32_t)*i, Bu::Fmt::hex(4).fill('0') ). end().getStr(), 6 ); else Bu::UtfString::writePoint( sOutput, *i ); break; } } sOutput.write("\"", 1 ); } Bu::Formatter &Bu::operator<<( Bu::Formatter &f, const Bu::Json &j ) { f.write( j.toString() ); return f; }