/* Copyright 2011 Eeli Reilin. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO * EVENT SHALL EELI REILIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation * are those of the authors and should not be interpreted as representing * official policies, either expressed or implied, of Eeli Reilin. */ /** * \file json.cpp */ #include "json.h" #include namespace QtJson { static QString sanitizeString(QString str) { str.replace(QLatin1String("\\"), QLatin1String("\\\\")); str.replace(QLatin1String("\""), QLatin1String("\\\"")); str.replace(QLatin1String("\b"), QLatin1String("\\b")); str.replace(QLatin1String("\f"), QLatin1String("\\f")); str.replace(QLatin1String("\n"), QLatin1String("\\n")); str.replace(QLatin1String("\r"), QLatin1String("\\r")); str.replace(QLatin1String("\t"), QLatin1String("\\t")); return QString(QLatin1String("\"%1\"")).arg(str); } static QByteArray join(const QList &list, const QByteArray &sep) { QByteArray res; Q_FOREACH(const QByteArray &i, list) { if(!res.isEmpty()) { res += sep; } res += i; } return res; } /** * parse */ QVariant Json::parse(const QString &json) { bool success = true; return Json::parse(json, success); } /** * parse */ QVariant Json::parse(const QString &json, bool &success) { success = true; //Return an empty QVariant if the JSON data is either null or empty if(!json.isNull() || !json.isEmpty()) { QString data = json; //We'll start from index 0 int index = 0; //Parse the first value QVariant value = Json::parseValue(data, index, success); //Return the parsed value return value; } else { //Return the empty QVariant return QVariant(); } } QByteArray Json::serialize(const QVariant &data) { bool success = true; return Json::serialize(data, success); } QByteArray Json::serialize(const QVariant &data, bool &success) { QByteArray str; success = true; if(!data.isValid()) // invalid or null? { str = "null"; } else if((data.type() == QVariant::List) || (data.type() == QVariant::StringList)) // variant is a list? { QList values; const QVariantList list = data.toList(); Q_FOREACH(const QVariant& v, list) { QByteArray serializedValue = serialize(v); if(serializedValue.isNull()) { success = false; break; } values << serializedValue; } str = "[ " + join( values, ", " ) + " ]"; } else if(data.type() == QVariant::Hash) // variant is a hash? { const QVariantHash vhash = data.toHash(); QHashIterator it( vhash ); str = "{ "; QList pairs; while(it.hasNext()) { it.next(); QByteArray serializedValue = serialize(it.value()); if(serializedValue.isNull()) { success = false; break; } pairs << sanitizeString(it.key()).toUtf8() + " : " + serializedValue; } str += join(pairs, ", "); str += " }"; } else if(data.type() == QVariant::Map) // variant is a map? { const QVariantMap vmap = data.toMap(); QMapIterator it( vmap ); str = "{ "; QList pairs; while(it.hasNext()) { it.next(); QByteArray serializedValue = serialize(it.value()); if(serializedValue.isNull()) { success = false; break; } pairs << sanitizeString(it.key()).toUtf8() + " : " + serializedValue; } str += join(pairs, ", "); str += " }"; } else if((data.type() == QVariant::String) || (data.type() == QVariant::ByteArray)) // a string or a byte array? { str = sanitizeString(data.toString()).toUtf8(); } else if(data.type() == QVariant::Double) // double? { str = QByteArray::number(data.toDouble(), 'g', 20); if(!str.contains(".") && ! str.contains("e")) { str += ".0"; } } else if (data.type() == QVariant::Bool) // boolean value? { str = data.toBool() ? "true" : "false"; } else if (data.type() == QVariant::ULongLong) // large unsigned number? { str = QByteArray::number(data.value()); } else if ( data.canConvert() ) // any signed number? { str = QByteArray::number(data.value()); } else if (data.canConvert()) { str = QString::number(data.value()).toUtf8(); } else if (data.canConvert()) // can value be converted to string? { // this will catch QDate, QDateTime, QUrl, ... str = sanitizeString(data.toString()).toUtf8(); } else { success = false; } if (success) { return str; } else { return QByteArray(); } } /** * parseValue */ QVariant Json::parseValue(const QString &json, int &index, bool &success) { //Determine what kind of data we should parse by //checking out the upcoming token switch(Json::lookAhead(json, index)) { case JsonTokenString: return Json::parseString(json, index, success); case JsonTokenNumber: return Json::parseNumber(json, index); case JsonTokenCurlyOpen: return Json::parseObject(json, index, success); case JsonTokenSquaredOpen: return Json::parseArray(json, index, success); case JsonTokenTrue: Json::nextToken(json, index); return QVariant(true); case JsonTokenFalse: Json::nextToken(json, index); return QVariant(false); case JsonTokenNull: Json::nextToken(json, index); return QVariant(); case JsonTokenNone: break; } //If there were no tokens, flag the failure and return an empty QVariant success = false; return QVariant(); } /** * parseObject */ QVariant Json::parseObject(const QString &json, int &index, bool &success) { QVariantMap map; int token; //Get rid of the whitespace and increment index Json::nextToken(json, index); //Loop through all of the key/value pairs of the object bool done = false; while(!done) { //Get the upcoming token token = Json::lookAhead(json, index); if(token == JsonTokenNone) { success = false; return QVariantMap(); } else if(token == JsonTokenComma) { Json::nextToken(json, index); } else if(token == JsonTokenCurlyClose) { Json::nextToken(json, index); return map; } else { //Parse the key/value pair's name QString name = Json::parseString(json, index, success).toString(); if(!success) { return QVariantMap(); } //Get the next token token = Json::nextToken(json, index); //If the next token is not a colon, flag the failure //return an empty QVariant if(token != JsonTokenColon) { success = false; return QVariant(QVariantMap()); } //Parse the key/value pair's value QVariant value = Json::parseValue(json, index, success); if(!success) { return QVariantMap(); } //Assign the value to the key in the map map[name] = value; } } //Return the map successfully return QVariant(map); } /** * parseArray */ QVariant Json::parseArray(const QString &json, int &index, bool &success) { QVariantList list; Json::nextToken(json, index); bool done = false; while(!done) { int token = Json::lookAhead(json, index); if(token == JsonTokenNone) { success = false; return QVariantList(); } else if(token == JsonTokenComma) { Json::nextToken(json, index); } else if(token == JsonTokenSquaredClose) { Json::nextToken(json, index); break; } else { QVariant value = Json::parseValue(json, index, success); if(!success) { return QVariantList(); } list.push_back(value); } } return QVariant(list); } /** * parseString */ QVariant Json::parseString(const QString &json, int &index, bool &success) { QString s; QChar c; Json::eatWhitespace(json, index); c = json[index++]; bool complete = false; while(!complete) { if(index == json.size()) { break; } c = json[index++]; if(c == '\"') { complete = true; break; } else if(c == '\\') { if(index == json.size()) { break; } c = json[index++]; if(c == '\"') { s.append('\"'); } else if(c == '\\') { s.append('\\'); } else if(c == '/') { s.append('/'); } else if(c == 'b') { s.append('\b'); } else if(c == 'f') { s.append('\f'); } else if(c == 'n') { s.append('\n'); } else if(c == 'r') { s.append('\r'); } else if(c == 't') { s.append('\t'); } else if(c == 'u') { int remainingLength = json.size() - index; if(remainingLength >= 4) { QString unicodeStr = json.mid(index, 4); int symbol = unicodeStr.toInt(0, 16); s.append(QChar(symbol)); index += 4; } else { break; } } } else { s.append(c); } } if(!complete) { success = false; return QVariant(); } return QVariant(s); } /** * parseNumber */ QVariant Json::parseNumber(const QString &json, int &index) { Json::eatWhitespace(json, index); int lastIndex = Json::lastIndexOfNumber(json, index); int charLength = (lastIndex - index) + 1; QString numberStr; numberStr = json.mid(index, charLength); index = lastIndex + 1; if (numberStr.contains('.')) { return QVariant(numberStr.toDouble(NULL)); } else if (numberStr.startsWith('-')) { return QVariant(numberStr.toLongLong(NULL)); } else { return QVariant(numberStr.toULongLong(NULL)); } } /** * lastIndexOfNumber */ int Json::lastIndexOfNumber(const QString &json, int index) { static const QString numericCharacters("0123456789+-.eE"); int lastIndex; for(lastIndex = index; lastIndex < json.size(); lastIndex++) { if(numericCharacters.indexOf(json[lastIndex]) == -1) { break; } } return lastIndex -1; } /** * eatWhitespace */ void Json::eatWhitespace(const QString &json, int &index) { static const QString whitespaceChars(" \t\n\r"); for(; index < json.size(); index++) { if(whitespaceChars.indexOf(json[index]) == -1) { break; } } } /** * lookAhead */ int Json::lookAhead(const QString &json, int index) { int saveIndex = index; return Json::nextToken(json, saveIndex); } /** * nextToken */ int Json::nextToken(const QString &json, int &index) { Json::eatWhitespace(json, index); if(index == json.size()) { return JsonTokenNone; } QChar c = json[index]; index++; switch(c.toLatin1()) { case '{': return JsonTokenCurlyOpen; case '}': return JsonTokenCurlyClose; case '[': return JsonTokenSquaredOpen; case ']': return JsonTokenSquaredClose; case ',': return JsonTokenComma; case '"': return JsonTokenString; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '-': return JsonTokenNumber; case ':': return JsonTokenColon; } index--; int remainingLength = json.size() - index; //True if(remainingLength >= 4) { if (json[index] == 't' && json[index + 1] == 'r' && json[index + 2] == 'u' && json[index + 3] == 'e') { index += 4; return JsonTokenTrue; } } //False if (remainingLength >= 5) { if (json[index] == 'f' && json[index + 1] == 'a' && json[index + 2] == 'l' && json[index + 3] == 's' && json[index + 4] == 'e') { index += 5; return JsonTokenFalse; } } //Null if (remainingLength >= 4) { if (json[index] == 'n' && json[index + 1] == 'u' && json[index + 2] == 'l' && json[index + 3] == 'l') { index += 4; return JsonTokenNull; } } return JsonTokenNone; } } //end namespace