#include "bu/json.h" #include "bu/staticmembuf.h" #include "bu/membuf.h" #include "bu/exceptionparse.h" #include "bu/sio.h" #include #define next( txt ) readChar( ps, "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::Json::ParseState &ps ) : eType( Invalid ) { parse( ps ); } 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::insertNull( const Bu::String &sKey ) { uDat.pObject->insert( sKey, new Json( Null ) ); return *this; } 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; } Bu::Json &Bu::Json::appendNull() { uDat.pArray->append( new Json( Null ) ); return *this; } void Bu::Json::parse( Bu::Stream &sInput ) { reset(); ParseState ps( sInput ); next("json"); parse( ps ); } void Bu::Json::parse( const Bu::String &sInput ) { Bu::StaticMemBuf mb( sInput.getStr(), sInput.getSize() ); parse( mb ); } void Bu::Json::parse( ParseState &ps ) { while( ps.c == ' ' || ps.c == '\t' || ps.c == '\r' || ps.c == '\n' ) { next( "json" ); } if( ps.c == '"' ) { // String parseString( ps ); } else if( ps.c == '{' ) { // Object parseObject( ps ); } else if( ps.c == '[' ) { // Array parseArray( ps ); } else if( ps.c == '-' || (ps.c >= '0' && ps.c <= '9') ) { // Number -- apparently they can't start with a period parseNumber( ps ); } else if( ps.c == 't' || ps.c == 'f' || ps.c == 'n' ) { // True / false / null parseLiteral( ps ); } else { ps.error( Bu::String("Invalid json: Invalid character: '%1'.").arg( (char)ps.c ) ); } } 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: { char buf[64]; if( uDat.dNumber == floor( uDat.dNumber ) ) { sprintf( buf, "%ld", (int64_t)uDat.dNumber ); } else { sprintf( buf, "%f", uDat.dNumber ); } sOutput.write( buf ); } 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; } bool Bu::Json::operator==( const Bu::String &rRhs ) { if( eType != String ) return false; return (*uDat.pString) == rRhs; } void Bu::Json::parseString( Bu::Json::ParseState &ps, Bu::UtfString &sOut ) { skipWs( ps ); bool bEscape = false; for(;;) { next( "string" ); if( bEscape ) { switch( ps.c ) { case '"': case '\\': case '/': sOut += ps.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': { char hex[5]; for( int j = 0; j < 4; j++ ) { readChar( ps ); if( (ps.c >= '0' && ps.c <= '9') || (ps.c >= 'a' && ps.c <= 'f') || (ps.c >= 'A' && ps.c <= 'F') ) { hex[j] = ps.c; } else { ps.error( "Invalid json: Invalid \\u " "escape sequence." ); } } hex[4] = '\0'; sOut += (Bu::UtfChar)strtol( hex, NULL, 16 ); } break; default: ps.error( Bu::String("Invalid json: Invalid escape sequence: " " '\\%1'.").arg( (char)ps.c ) ); break; } bEscape = false; } else { if( ps.c == '\\' ) bEscape = true; else if( ps.c == '"' ) { readChar( ps ); break; } else sOut += ps.c; } } } void Bu::Json::parseString( Bu::Json::ParseState &ps ) { eType = String; uDat.pString = new Bu::UtfString(); parseString( ps, *uDat.pString ); } void Bu::Json::parseObject( Bu::Json::ParseState &ps ) { skipWs( ps ); eType = Object; uDat.pObject = new JsonHash(); next( "object" ); skipWs( ps ); // Check to see if it's an empty object. if( ps.c == '}' ) { next("object"); return; } for(;;) { skipWs( ps ); if( ps.c != '"' ) { ps.error( Bu::String("Invalid json: expected string as key in object, " "found '%1'.").arg( (char)ps.c ) ); } Bu::UtfString sKey; parseString( ps, sKey ); skipWs( ps ); if( ps.c != ':' ) { ps.error( Bu::String("Invalid json: expected colon after key in object, " "found '%1'.").arg( (char)ps.c ) ); } next("object"); uDat.pObject->insert( sKey, new Json( ps ) ); skipWs( ps ); if( ps.c == '}' ) { readChar( ps ); break; } else if( ps.c == ',' ) next( "object" ); else ps.error( Bu::String("Invalid json: expected comma or } after value " "in object, found '%1'.").arg( (char)ps.c ) ); } } void Bu::Json::parseArray( Bu::Json::ParseState &ps ) { skipWs( ps ); eType = Array; uDat.pArray = new JsonList(); next("array"); // Check to see if it's an empty array. if( ps.c == ']' ) { next("array"); return; } for(;;) { uDat.pArray->append( new Json( ps ) ); skipWs( ps ); if( ps.c == ']' ) { readChar( ps ); break; } else if( ps.c == ',' ) { next("array"); continue; } else { ps.error( Bu::String("Invalid json: expected comma or ] after value " "in array, found '%1'.").arg( (char)ps.c ) ); } } } void Bu::Json::parseNumber( Bu::Json::ParseState &ps ) { skipWs( ps ); Bu::String sBuf; if( ps.c == '-' ) { sBuf += ps.c; next( "number" ); } bool bIntPart = true; do { if( ps.c >= '0' && ps.c <= '9' ) sBuf += ps.c; else if( ps.c == '.' && bIntPart == true ) { bIntPart = false; sBuf += ps.c; } else if( ps.c == ' ' || ps.c == '\t' || ps.c == '\n' || ps.c == '\r' || ps.c == '}' || ps.c == ']' || ps.c == ',' ) { break; } else { ps.error( Bu::String("Invalid json: Invalid character in number: '%1'."). arg( (char)ps.c ) ); } } while( readChar( ps ) ); eType = Number; uDat.dNumber = atof( sBuf.getStr() ); } void Bu::Json::parseLiteral( Bu::Json::ParseState &ps ) { skipWs( ps ); Bu::String s; do { if( isWs( ps.c ) || ps.c == ',' || ps.c == '}' || ps.c == ']' ) break; else s += ps.c; } while( readChar( ps ) ); 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 { ps.error( Bu::String("Invalid json: Invalid literal token found, '%1'."). arg( s ) ); } } bool Bu::Json::readChar( Bu::Json::ParseState &ps ) { if( Bu::UtfString::readPoint( ps.sInput, ps.c ) == 0 && ps.sInput.isEos() ) return false; if( ps.c == '\n' ) { // Increment the line and set iChar to zero. This makes sense only // beacuse we only complain after a charecter has been read, so this // will be too large by one unless we start at zero. ps.iLine++; ps.iChar = 0; } else { ps.iChar++; } return true; } void Bu::Json::readChar( Bu::Json::ParseState &ps, const char *sSection ) { if( !readChar( ps ) ) { ps.error( sSection ); } } bool Bu::Json::isWs( Bu::UtfChar c ) { return c == ' ' || c == '\t' || c == '\r' || c == '\n'; } void Bu::Json::skipWs( Bu::Json::ParseState &ps ) { while( isWs( ps.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; } void Bu::Json::ParseState::error( const Bu::String &sTxt ) { throw Bu::ExceptionParse( Bu::String("%1:%2: %3"). arg( iLine ).arg( iChar ).arg( sTxt ).end().getStr() ); }