patterncppMinor
Yet another JSON parser and serializer for Qt, but with additional features
Viewed 0 times
parserserializerbutandwithyetanotherforjsonfeatures
Problem
I wrote
Features
QJson uses QVariants for parsed values and QStrings for serialized JSON data.
The parser not only understands the JSON language but optionally also this modificatons for "lazy" writers, providing the ability to write the data in a simpler way:
The serializer has the following features:
-
Format output either compact or nice indented, e.g. compact:
-
Treat unknown types either as an error (cancel serialization and return
The interface is similar to PHP's functions
where the
QJson, a utility class in/for Qt, I need you to take a look at. It is both a JSON parser and serializer, but with extended functionality (going beyond the JSON specification) (see first section of this post).Features
QJson uses QVariants for parsed values and QStrings for serialized JSON data.
The parser not only understands the JSON language but optionally also this modificatons for "lazy" writers, providing the ability to write the data in a simpler way:
- Allow unquoted strings (e.g.
{foo: bar}instead of{"foo": "bar"}. The end of a string is assumed when either a new-line, non-escaped colon, comma,]or}is following. The whole string is then trimmed. Strings starting with digits are not allowed. Escaping within the string is allowed, even\followed by any character is treated like the character itself (if it isn't a special escape sequence like\n). The keywordstrue,false,nullare still parsed as a boolean / null values, not as strings.
- Allow arrays and objects with whitespaces instead of commas as their separator, also mixed (e.g.
{whitespaces only: [1 2 3], commas only: [1,2,3], mixed: [1, 2 3]}.
- Allow any type for the whole JSON (JSON spec only allows objects and arrays as root values), even empty to simplify error handling.
- Parse errors are (optionally) reported in a human-readable output (e.g.
{foo: bar=> "Unexpected end of JSON input.",[foo: bar]=> "Unexpected character at index 4").
The serializer has the following features:
-
Format output either compact or nice indented, e.g. compact:
[1,2,3,{"foo":"bar"}] vs. indented:[
1,
2,
3,
{
"foo" : "bar"
}
]-
Treat unknown types either as an error (cancel serialization and return
"") or encode them as null.The interface is similar to PHP's functions
json_encode, json_decode:QString QJson::encode(const QVariant &data, (...) );
QVariant QJson::decode(const QString &json, (...) );where the
Solution
In general
I like that you didn't try and force OO on something that works very well without it, but it would be simpler, cleaner if you were to just put all those functions in a namespace instead of a class (IIRC the metadata-generating macros for enums work outside classes, too). you can use global static functions or anonymous namespaces in your .cpp file for the private stuff. And also, it is very nice that you don't try to handle I/O and some other stuff that library writers are so keen on doing. SRP all the way, it's great :)
Error handling
There is one thing I don't particularly like about your code:
I can tell by your coding style that you try to follow the Qt coding guidelines, which is fantastic for a Qt library, but this kind of error handling is not particularly useful. You should stick to the
Possible improvements
There is some unneeded duplication, for example
Consistency in style: it's pretty good, and again, props for sticking to the Qt guidleines. One thing I noticed though, is that you use parentheses around case labels (which is alright, if somewhat alien to me personally), but you forget it in some places.
Other
The serializer understands both QVariantList and QStringList, which
will be encoded in exactly the same way. So the codes look exactly the
same, too. I want to put them in one case, but don't know how.
Simple (if I understood your intention correctly):
Additional features
I think the QObject-serialization is already possible with QScriptEngine (although you can probably implement it smaller). One thing I would love to see is an implementation of json-schema. It is still a draft, but there are already some implementations out there (you can google them). It adds validation capability to JSON (probably not as powerful as XML schemas and DTD, but it's something).
I like that you didn't try and force OO on something that works very well without it, but it would be simpler, cleaner if you were to just put all those functions in a namespace instead of a class (IIRC the metadata-generating macros for enums work outside classes, too). you can use global static functions or anonymous namespaces in your .cpp file for the private stuff. And also, it is very nice that you don't try to handle I/O and some other stuff that library writers are so keen on doing. SRP all the way, it's great :)
Error handling
There is one thing I don't particularly like about your code:
QString *errorMessageI can tell by your coding style that you try to follow the Qt coding guidelines, which is fantastic for a Qt library, but this kind of error handling is not particularly useful. You should stick to the
qWarning/qDebug etc. kind of error reporting, since Q_ASSERT does that anyway (they can be redirected from external code, without modifying your library if your user wants to). Or even: use exceptions - Qt is huge, so avoiding exceptions does result in a great size reduction for them, but your little library would not suffer that much. The problem with the current approach is that it limits the caller to one way of handling the error: presenting the (by the way not localized) string to the user.Possible improvements
There is some unneeded duplication, for example
parseString and parseUnquotedString looks quite similar, and I noticed other parts, too.Consistency in style: it's pretty good, and again, props for sticking to the Qt guidleines. One thing I noticed though, is that you use parentheses around case labels (which is alright, if somewhat alien to me personally), but you forget it in some places.
Other
The serializer understands both QVariantList and QStringList, which
will be encoded in exactly the same way. So the codes look exactly the
same, too. I want to put them in one case, but don't know how.
Simple (if I understood your intention correctly):
case(QVariant::List):
case(QVariant::StringList):
{
encoded = QString::fromAscii("[") + optionalIndentedNewLine;
QStringList list = data.toStringList();
for(int i = 0; i isNull())
return QString();
}
encoded += optionalNewLine + QString::fromAscii("]");
}
break;Additional features
I think the QObject-serialization is already possible with QScriptEngine (although you can probably implement it smaller). One thing I would love to see is an implementation of json-schema. It is still a draft, but there are already some implementations out there (you can google them). It adds validation capability to JSON (probably not as powerful as XML schemas and DTD, but it's something).
Code Snippets
QString *errorMessagecase(QVariant::List):
case(QVariant::StringList):
{
encoded = QString::fromAscii("[") + optionalIndentedNewLine;
QStringList list = data.toStringList();
for(int i = 0; i < list.count(); ++i)
{
if(i) encoded += QString::fromAscii(",") + optionalIndentedNewLine;
encoded += encodeData(list.at(i), options, errorMessage, indentation, indentedLinePrefix);
if(errorMessage && !errorMessage->isNull())
return QString();
}
encoded += optionalNewLine + QString::fromAscii("]");
}
break;Context
StackExchange Code Review Q#11927, answer score: 4
Revisions (0)
No revisions yet.