QJson: Allow parsing any JSON value to QJsonValue

QJsonValue can now parse all its types (arrays, strings, objects,
numbers, booleans, and null) at the top-level. QJsonDocument will still
return an error when parsing types that aren't objects or arrays.

[ChangeLog][QtCore][QJsonValue] QJsonValue now follows RFC 8259 and is
thus able to parse any JSON value, not just arrays and objects.
QJsonDocument remains at the level of RFC 4627 in that only arrays and
objects can be parsed.

Fixes: QTBUG-62502
Change-Id: I10f3895a7646953a6f6b5f132196267e700782a1
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Johannes Grunenberg 2024-11-13 18:08:55 +01:00
parent 5c3f768409
commit 65fda988e9
8 changed files with 394 additions and 96 deletions

View File

@ -19,7 +19,7 @@
modify and save JSON data. modify and save JSON data.
More details about the JSON data format can be found at \l{http://json.org}{json.org} More details about the JSON data format can be found at \l{http://json.org}{json.org}
and in \l {RFC 4627}. and in \l {RFC 8259}.
\section1 Overview \section1 Overview

View File

@ -253,6 +253,12 @@ public:
{ {
insertAt(elements.size(), v); insertAt(elements.size(), v);
} }
void append(QCborValue &&v)
{
insertAt(elements.size(), v, MoveContainer);
v.container = nullptr;
v.t = QCborValue::Undefined;
}
QByteArray byteArrayAt(qsizetype idx) const QByteArray byteArrayAt(qsizetype idx) const
{ {

View File

@ -272,6 +272,11 @@ QJsonDocument QJsonDocument::fromJson(const QByteArray &json, QJsonParseError *e
if (val.isArray() || val.isMap()) { if (val.isArray() || val.isMap()) {
result.d = std::make_unique<QJsonDocumentPrivate>(); result.d = std::make_unique<QJsonDocumentPrivate>();
result.d->value = val; result.d->value = val;
} else if (!val.isUndefined() && error) {
// parsed a valid string/number/bool/null,
// but QJsonDocument only stores objects and arrays.
error->error = QJsonParseError::IllegalValue;
error->offset = 0;
} }
return result; return result;
} }

View File

@ -61,7 +61,7 @@ using namespace QtMiscUtils;
\value UnterminatedArray The array is not correctly terminated with a closing square bracket \value UnterminatedArray The array is not correctly terminated with a closing square bracket
\value MissingValueSeparator A colon separating keys from values inside objects is missing \value MissingValueSeparator A colon separating keys from values inside objects is missing
\value IllegalValue The value is illegal \value IllegalValue The value is illegal
\value TerminationByNumber The input stream ended while parsing a number \value TerminationByNumber The input stream ended while parsing a number (as of 6.9, this is no longer returned)
\value IllegalNumber The number is not well formed \value IllegalNumber The number is not well formed
\value IllegalEscapeSequence An illegal escape sequence occurred in the input \value IllegalEscapeSequence An illegal escape sequence occurred in the input
\value IllegalUTF8String An illegal UTF8 sequence occurred in the input \value IllegalUTF8String An illegal UTF8 sequence occurred in the input
@ -161,21 +161,20 @@ class StashedContainer
public: public:
StashedContainer(QExplicitlySharedDataPointer<QCborContainerPrivate> *container, StashedContainer(QExplicitlySharedDataPointer<QCborContainerPrivate> *container,
QCborValue::Type type) QCborValue::Type type)
: type(type), stashed(std::move(*container)), current(container) : type(type), stashed(std::move(*container))
{ {
} }
~StashedContainer() QCborValue intoValue(QExplicitlySharedDataPointer<QCborContainerPrivate> *parent)
{ {
stashed->append(QCborContainerPrivate::makeValue(type, -1, current->take(), std::swap(stashed, *parent);
QCborContainerPrivate::MoveContainer)); return QCborContainerPrivate::makeValue(type, -1, stashed.take(),
*current = std::move(stashed); QCborContainerPrivate::MoveContainer);
} }
private: private:
QCborValue::Type type; QCborValue::Type type;
QExplicitlySharedDataPointer<QCborContainerPrivate> stashed; QExplicitlySharedDataPointer<QCborContainerPrivate> stashed;
QExplicitlySharedDataPointer<QCborContainerPrivate> *current;
}; };
Parser::Parser(const char *json, int length) Parser::Parser(const char *json, int length)
@ -281,27 +280,29 @@ char Parser::nextToken()
QCborValue Parser::parse(QJsonParseError *error) QCborValue Parser::parse(QJsonParseError *error)
{ {
eatBOM(); eatBOM();
char token = nextToken();
QCborValue data; char token;
QCborValue value;
if (token == BeginArray) { if (!eatSpace()) {
container = new QCborContainerPrivate;
if (!parseArray())
goto error;
data = QCborContainerPrivate::makeValue(QCborValue::Array, -1, container.take(),
QCborContainerPrivate::MoveContainer);
} else if (token == BeginObject) {
container = new QCborContainerPrivate;
if (!parseObject())
goto error;
data = QCborContainerPrivate::makeValue(QCborValue::Map, -1, container.take(),
QCborContainerPrivate::MoveContainer);
} else {
lastError = QJsonParseError::IllegalValue; lastError = QJsonParseError::IllegalValue;
goto error; goto error;
} }
token = *json;
if (token == Quote) {
container = new QCborContainerPrivate;
json++;
if (!parseString())
goto error;
value = QCborContainerPrivate::makeValue(QCborValue::String, 0, container.take(),
QCborContainerPrivate::MoveContainer);
} else {
value = parseValue();
if (value.isUndefined())
goto error;
}
eatSpace(); eatSpace();
if (json < end) { if (json < end) {
lastError = QJsonParseError::GarbageAtEnd; lastError = QJsonParseError::GarbageAtEnd;
@ -314,7 +315,7 @@ QCborValue Parser::parse(QJsonParseError *error)
error->error = QJsonParseError::NoError; error->error = QJsonParseError::NoError;
} }
return data; return value;
} }
error: error:
@ -423,6 +424,20 @@ static void sortContainer(QCborContainerPrivate *container)
container->elements.erase(result.elementsIterator(), container->elements.end()); container->elements.erase(result.elementsIterator(), container->elements.end());
} }
bool Parser::parseValueIntoContainer()
{
QCborValue value = parseValue();
switch (value.type()) {
case QCborValue::Undefined:
return false; // error while parsing
case QCborValue::String:
break; // strings were already added
default:
container->append(std::move(value));
}
return true;
}
/* /*
object = begin-object [ member *( value-separator member ) ] object = begin-object [ member *( value-separator member ) ]
@ -480,10 +495,8 @@ bool Parser::parseMember()
lastError = QJsonParseError::UnterminatedObject; lastError = QJsonParseError::UnterminatedObject;
return false; return false;
} }
if (!parseValue())
return false;
return true; return parseValueIntoContainer();
} }
/* /*
@ -510,8 +523,10 @@ bool Parser::parseArray()
} }
if (!container) if (!container)
container = new QCborContainerPrivate; container = new QCborContainerPrivate;
if (!parseValue())
if (!parseValueIntoContainer())
return false; return false;
char token = nextToken(); char token = nextToken();
if (token == EndArray) if (token == EndArray)
break; break;
@ -535,82 +550,81 @@ value = false / null / true / object / array / number / string
*/ */
bool Parser::parseValue() QCborValue Parser::parseValue()
{ {
switch (*json++) { switch (*json++) {
case 'n': case 'n':
if (end - json < 4) { if (end - json < 3) {
lastError = QJsonParseError::IllegalValue; lastError = QJsonParseError::IllegalValue;
return false; return QCborValue();
} }
if (*json++ == 'u' && if (*json++ == 'u' &&
*json++ == 'l' && *json++ == 'l' &&
*json++ == 'l') { *json++ == 'l') {
container->append(QCborValue(QCborValue::Null)); return QCborValue(QCborValue::Null);
return true;
} }
lastError = QJsonParseError::IllegalValue; lastError = QJsonParseError::IllegalValue;
return false; return QCborValue();
case 't': case 't':
if (end - json < 4) { if (end - json < 3) {
lastError = QJsonParseError::IllegalValue; lastError = QJsonParseError::IllegalValue;
return false; return QCborValue();
} }
if (*json++ == 'r' && if (*json++ == 'r' &&
*json++ == 'u' && *json++ == 'u' &&
*json++ == 'e') { *json++ == 'e') {
container->append(QCborValue(true)); return QCborValue(true);
return true;
} }
lastError = QJsonParseError::IllegalValue; lastError = QJsonParseError::IllegalValue;
return false; return QCborValue();
case 'f': case 'f':
if (end - json < 5) { if (end - json < 4) {
lastError = QJsonParseError::IllegalValue; lastError = QJsonParseError::IllegalValue;
return false; return QCborValue();
} }
if (*json++ == 'a' && if (*json++ == 'a' &&
*json++ == 'l' && *json++ == 'l' &&
*json++ == 's' && *json++ == 's' &&
*json++ == 'e') { *json++ == 'e') {
container->append(QCborValue(false)); return QCborValue(false);
return true;
} }
lastError = QJsonParseError::IllegalValue; lastError = QJsonParseError::IllegalValue;
return false; return QCborValue();
case Quote: { case Quote: {
if (!parseString()) if (parseString())
return false; // strings are already added to the container
return true; // callers must check for this type
return QCborValue(QCborValue::String);
return QCborValue();
} }
case BeginArray: { case BeginArray: {
StashedContainer stashedContainer(&container, QCborValue::Array); StashedContainer stashedContainer(&container, QCborValue::Array);
if (!parseArray()) if (parseArray())
return false; return stashedContainer.intoValue(&container);
return true;
return QCborValue();
} }
case BeginObject: { case BeginObject: {
StashedContainer stashedContainer(&container, QCborValue::Map); StashedContainer stashedContainer(&container, QCborValue::Map);
if (!parseObject()) if (parseObject())
return false; return stashedContainer.intoValue(&container);
return true;
return QCborValue();
} }
case ValueSeparator: case ValueSeparator:
// Essentially missing value, but after a colon, not after a comma // Essentially missing value, but after a colon, not after a comma
// like the other MissingObject errors. // like the other MissingObject errors.
lastError = QJsonParseError::IllegalValue; lastError = QJsonParseError::IllegalValue;
return false; return QCborValue();
case EndObject: case EndObject:
case EndArray: case EndArray:
lastError = QJsonParseError::MissingObject; lastError = QJsonParseError::MissingObject;
return false; return QCborValue();
default: default:
--json; --json;
if (!parseNumber()) return parseNumber();
return false;
} }
return true;
} }
@ -631,7 +645,7 @@ bool Parser::parseValue()
*/ */
bool Parser::parseNumber() QCborValue Parser::parseNumber()
{ {
const char *start = json; const char *start = json;
bool isInt = true; bool isInt = true;
@ -667,19 +681,13 @@ bool Parser::parseNumber()
++json; ++json;
} }
if (json >= end) {
lastError = QJsonParseError::TerminationByNumber;
return false;
}
const QByteArray number = QByteArray::fromRawData(start, json - start); const QByteArray number = QByteArray::fromRawData(start, json - start);
if (isInt) { if (isInt) {
bool ok; bool ok;
qlonglong n = number.toLongLong(&ok); qlonglong n = number.toLongLong(&ok);
if (ok) { if (ok) {
container->append(QCborValue(n)); return QCborValue(n);
return true;
} }
} }
@ -688,16 +696,13 @@ bool Parser::parseNumber()
if (!ok) { if (!ok) {
lastError = QJsonParseError::IllegalNumber; lastError = QJsonParseError::IllegalNumber;
return false; return QCborValue();
} }
qint64 n; qint64 n;
if (convertDoubleTo(d, &n)) if (convertDoubleTo(d, &n))
container->append(QCborValue(n)); return QCborValue(n);
else return QCborValue(d);
container->append(QCborValue(d));
return true;
} }
/* /*
@ -819,7 +824,7 @@ bool Parser::parseString()
isAscii = false; isAscii = false;
} }
++json; ++json;
if (json >= end) { if (json > end) {
lastError = QJsonParseError::UnterminatedString; lastError = QJsonParseError::UnterminatedString;
return false; return false;
} }
@ -855,7 +860,7 @@ bool Parser::parseString()
} }
++json; ++json;
if (json >= end) { if (json > end) {
lastError = QJsonParseError::UnterminatedString; lastError = QJsonParseError::UnterminatedString;
return false; return false;
} }

View File

@ -39,8 +39,9 @@ private:
bool parseArray(); bool parseArray();
bool parseMember(); bool parseMember();
bool parseString(); bool parseString();
bool parseValue(); bool parseValueIntoContainer();
bool parseNumber(); QCborValue parseValue();
QCborValue parseNumber();
const char *head; const char *head;
const char *json; const char *json;
const char *end; const char *end;

View File

@ -605,8 +605,6 @@ QVariant QJsonValue::toVariant() const
the optional \a error variable will contain further details about the the optional \a error variable will contain further details about the
error. error.
Currently, only objects/maps and arrays/lists can be parsed.
\sa QJsonParseError, isUndefined(), toJson() \sa QJsonParseError, isUndefined(), toJson()
*/ */
QJsonValue QJsonValue::fromJson(QByteArrayView json, QJsonParseError *error) QJsonValue QJsonValue::fromJson(QByteArrayView json, QJsonParseError *error)

View File

@ -93,6 +93,10 @@ private Q_SLOTS:
void parseNumbers(); void parseNumbers();
void parseStrings(); void parseStrings();
void parseDuplicateKeys(); void parseDuplicateKeys();
void parseTopLevel_data();
void parseTopLevel();
void parseTopLevelErrors_data();
void parseTopLevelErrors();
void testParser(); void testParser();
void assignToDocument(); void assignToDocument();
@ -2278,161 +2282,313 @@ void tst_QtJson::fromJsonErrors()
{ {
{ {
QJsonParseError error; QJsonParseError error;
QJsonParseError error2;
QByteArray json = "{\n \n\n"; QByteArray json = "{\n \n\n";
QJsonValue val = QJsonValue::fromJson(json, &error); QJsonValue val = QJsonValue::fromJson(json, &error);
QJsonDocument doc = QJsonDocument::fromJson(json, &error2);
QVERIFY(val.isUndefined()); QVERIFY(val.isUndefined());
QVERIFY(doc.isEmpty());
QCOMPARE(error.error, error2.error);
QCOMPARE(error.offset, error2.offset);
QCOMPARE(error.error, QJsonParseError::UnterminatedObject); QCOMPARE(error.error, QJsonParseError::UnterminatedObject);
QCOMPARE(error.offset, 8); QCOMPARE(error.offset, 8);
} }
{ {
QJsonParseError error; QJsonParseError error;
QJsonParseError error2;
QByteArray json = "{\n \"key\" 10\n"; QByteArray json = "{\n \"key\" 10\n";
QJsonValue val = QJsonValue::fromJson(json, &error); QJsonValue val = QJsonValue::fromJson(json, &error);
QJsonDocument doc = QJsonDocument::fromJson(json, &error2);
QVERIFY(val.isUndefined()); QVERIFY(val.isUndefined());
QVERIFY(doc.isEmpty());
QCOMPARE(error.error, error2.error);
QCOMPARE(error.offset, error2.offset);
QCOMPARE(error.error, QJsonParseError::MissingNameSeparator); QCOMPARE(error.error, QJsonParseError::MissingNameSeparator);
QCOMPARE(error.offset, 13); QCOMPARE(error.offset, 13);
} }
{ {
QJsonParseError error; QJsonParseError error;
QJsonParseError error2;
QByteArray json = "[\n \n\n"; QByteArray json = "[\n \n\n";
QJsonValue val = QJsonValue::fromJson(json, &error); QJsonValue val = QJsonValue::fromJson(json, &error);
QJsonDocument doc = QJsonDocument::fromJson(json, &error2);
QVERIFY(val.isUndefined()); QVERIFY(val.isUndefined());
QVERIFY(doc.isEmpty());
QCOMPARE(error.error, error2.error);
QCOMPARE(error.offset, error2.offset);
QCOMPARE(error.error, QJsonParseError::UnterminatedArray); QCOMPARE(error.error, QJsonParseError::UnterminatedArray);
QCOMPARE(error.offset, 8); QCOMPARE(error.offset, 8);
} }
{ {
QJsonParseError error; QJsonParseError error;
QJsonParseError error2;
QByteArray json = "[\n 1, true\n\n"; QByteArray json = "[\n 1, true\n\n";
QJsonValue val = QJsonValue::fromJson(json, &error); QJsonValue val = QJsonValue::fromJson(json, &error);
QJsonDocument doc = QJsonDocument::fromJson(json, &error2);
QVERIFY(val.isUndefined()); QVERIFY(val.isUndefined());
QVERIFY(doc.isEmpty());
QCOMPARE(error.error, error2.error);
QCOMPARE(error.offset, error2.offset);
QCOMPARE(error.error, QJsonParseError::UnterminatedArray); QCOMPARE(error.error, QJsonParseError::UnterminatedArray);
QCOMPARE(error.offset, 14); QCOMPARE(error.offset, 14);
} }
{ {
QJsonParseError error; QJsonParseError error;
QJsonParseError error2;
QByteArray json = "[\n 1 true\n\n"; QByteArray json = "[\n 1 true\n\n";
QJsonValue val = QJsonValue::fromJson(json, &error); QJsonValue val = QJsonValue::fromJson(json, &error);
QJsonDocument doc = QJsonDocument::fromJson(json, &error2);
QVERIFY(val.isUndefined()); QVERIFY(val.isUndefined());
QVERIFY(doc.isEmpty());
QCOMPARE(error.error, error2.error);
QCOMPARE(error.offset, error2.offset);
QCOMPARE(error.error, QJsonParseError::MissingValueSeparator); QCOMPARE(error.error, QJsonParseError::MissingValueSeparator);
QCOMPARE(error.offset, 7); QCOMPARE(error.offset, 7);
} }
{ {
QJsonParseError error; QJsonParseError error;
QJsonParseError error2;
QByteArray json = "[\n nul"; QByteArray json = "[\n nul";
QJsonValue val = QJsonValue::fromJson(json, &error); QJsonValue val = QJsonValue::fromJson(json, &error);
QJsonDocument doc = QJsonDocument::fromJson(json, &error2);
QVERIFY(val.isUndefined()); QVERIFY(val.isUndefined());
QVERIFY(doc.isEmpty());
QCOMPARE(error.error, error2.error);
QCOMPARE(error.offset, error2.offset);
QCOMPARE(error.error, QJsonParseError::IllegalValue); QCOMPARE(error.error, QJsonParseError::IllegalValue);
QCOMPARE(error.offset, 7); QCOMPARE(error.offset, 7);
} }
{ {
QJsonParseError error; QJsonParseError error;
QJsonParseError error2;
QByteArray json = "[\n nulzz"; QByteArray json = "[\n nulzz";
QJsonValue val = QJsonValue::fromJson(json, &error); QJsonValue val = QJsonValue::fromJson(json, &error);
QJsonDocument doc = QJsonDocument::fromJson(json, &error2);
QVERIFY(val.isUndefined()); QVERIFY(val.isUndefined());
QVERIFY(doc.isEmpty());
QCOMPARE(error.error, error2.error);
QCOMPARE(error.offset, error2.offset);
QCOMPARE(error.error, QJsonParseError::IllegalValue); QCOMPARE(error.error, QJsonParseError::IllegalValue);
QCOMPARE(error.offset, 10); QCOMPARE(error.offset, 10);
} }
{ {
QJsonParseError error; QJsonParseError error;
QJsonParseError error2;
QByteArray json = "[\n tru"; QByteArray json = "[\n tru";
QJsonValue val = QJsonValue::fromJson(json, &error); QJsonValue val = QJsonValue::fromJson(json, &error);
QJsonDocument doc = QJsonDocument::fromJson(json, &error2);
QVERIFY(val.isUndefined()); QVERIFY(val.isUndefined());
QVERIFY(doc.isEmpty());
QCOMPARE(error.error, error2.error);
QCOMPARE(error.offset, error2.offset);
QCOMPARE(error.error, QJsonParseError::IllegalValue); QCOMPARE(error.error, QJsonParseError::IllegalValue);
QCOMPARE(error.offset, 7); QCOMPARE(error.offset, 7);
} }
{ {
QJsonParseError error; QJsonParseError error;
QJsonParseError error2;
QByteArray json = "[\n trud]"; QByteArray json = "[\n trud]";
QJsonValue val = QJsonValue::fromJson(json, &error); QJsonValue val = QJsonValue::fromJson(json, &error);
QJsonDocument doc = QJsonDocument::fromJson(json, &error2);
QVERIFY(val.isUndefined()); QVERIFY(val.isUndefined());
QVERIFY(doc.isEmpty());
QCOMPARE(error.error, error2.error);
QCOMPARE(error.offset, error2.offset);
QCOMPARE(error.error, QJsonParseError::IllegalValue); QCOMPARE(error.error, QJsonParseError::IllegalValue);
QCOMPARE(error.offset, 10); QCOMPARE(error.offset, 10);
} }
{ {
QJsonParseError error; QJsonParseError error;
QJsonParseError error2;
QByteArray json = "[\n fal"; QByteArray json = "[\n fal";
QJsonValue val = QJsonValue::fromJson(json, &error); QJsonValue val = QJsonValue::fromJson(json, &error);
QJsonDocument doc = QJsonDocument::fromJson(json, &error2);
QVERIFY(val.isUndefined()); QVERIFY(val.isUndefined());
QVERIFY(doc.isEmpty());
QCOMPARE(error.error, error2.error);
QCOMPARE(error.offset, error2.offset);
QCOMPARE(error.error, QJsonParseError::IllegalValue); QCOMPARE(error.error, QJsonParseError::IllegalValue);
QCOMPARE(error.offset, 7); QCOMPARE(error.offset, 7);
} }
{ {
QJsonParseError error; QJsonParseError error;
QJsonParseError error2;
QByteArray json = "[\n falsd]"; QByteArray json = "[\n falsd]";
QJsonValue val = QJsonValue::fromJson(json, &error); QJsonValue val = QJsonValue::fromJson(json, &error);
QJsonDocument doc = QJsonDocument::fromJson(json, &error2);
QVERIFY(val.isUndefined()); QVERIFY(val.isUndefined());
QVERIFY(doc.isEmpty());
QCOMPARE(error.error, error2.error);
QCOMPARE(error.offset, error2.offset);
QCOMPARE(error.error, QJsonParseError::IllegalValue); QCOMPARE(error.error, QJsonParseError::IllegalValue);
QCOMPARE(error.offset, 11); QCOMPARE(error.offset, 11);
} }
{ {
QJsonParseError error; QJsonParseError error;
QJsonParseError error2;
QByteArray json = "[false";
QJsonValue val = QJsonValue::fromJson(json, &error);
QJsonDocument doc = QJsonDocument::fromJson(json, &error2);
QVERIFY(val.isUndefined());
QVERIFY(doc.isEmpty());
QCOMPARE(error.error, error2.error);
QCOMPARE(error.offset, error2.offset);
QCOMPARE(error.error, QJsonParseError::UnterminatedArray);
QCOMPARE(error.offset, 6);
}
{
QJsonParseError error;
QJsonParseError error2;
QByteArray json = "[true";
QJsonValue val = QJsonValue::fromJson(json, &error);
QJsonDocument doc = QJsonDocument::fromJson(json, &error2);
QVERIFY(val.isUndefined());
QVERIFY(doc.isEmpty());
QCOMPARE(error.error, error2.error);
QCOMPARE(error.offset, error2.offset);
QCOMPARE(error.error, QJsonParseError::UnterminatedArray);
QCOMPARE(error.offset, 5);
}
{
QJsonParseError error;
QJsonParseError error2;
QByteArray json = "[null";
QJsonValue val = QJsonValue::fromJson(json, &error);
QJsonDocument doc = QJsonDocument::fromJson(json, &error2);
QVERIFY(val.isUndefined());
QVERIFY(doc.isEmpty());
QCOMPARE(error.error, error2.error);
QCOMPARE(error.offset, error2.offset);
QCOMPARE(error.error, QJsonParseError::UnterminatedArray);
QCOMPARE(error.offset, 5);
}
{
QJsonParseError error;
QJsonParseError error2;
QByteArray json = "[\n 11111"; QByteArray json = "[\n 11111";
QJsonValue val = QJsonValue::fromJson(json, &error); QJsonValue val = QJsonValue::fromJson(json, &error);
QJsonDocument doc = QJsonDocument::fromJson(json, &error2);
QVERIFY(val.isUndefined()); QVERIFY(val.isUndefined());
QCOMPARE(error.error, QJsonParseError::TerminationByNumber); QVERIFY(doc.isEmpty());
QCOMPARE(error.error, error2.error);
QCOMPARE(error.offset, error2.offset);
QCOMPARE(error.error, QJsonParseError::UnterminatedArray);
QCOMPARE(error.offset, 11); QCOMPARE(error.offset, 11);
} }
{ {
QJsonParseError error; QJsonParseError error;
QJsonParseError error2;
QByteArray json = "{\n \"foo\": 0 ";
QJsonValue val = QJsonValue::fromJson(json, &error);
QJsonDocument doc = QJsonDocument::fromJson(json, &error2);
QVERIFY(val.isUndefined());
QVERIFY(doc.isEmpty());
QCOMPARE(error.error, error2.error);
QCOMPARE(error.offset, error2.offset);
QCOMPARE(error.error, QJsonParseError::UnterminatedObject);
QCOMPARE(error.offset, 15);
}
{
QJsonParseError error;
QJsonParseError error2;
QByteArray json = "[\n -1E10000]"; QByteArray json = "[\n -1E10000]";
QJsonValue val = QJsonValue::fromJson(json, &error); QJsonValue val = QJsonValue::fromJson(json, &error);
QJsonDocument doc = QJsonDocument::fromJson(json, &error2);
QVERIFY(val.isUndefined()); QVERIFY(val.isUndefined());
QVERIFY(doc.isEmpty());
QCOMPARE(error.error, error2.error);
QCOMPARE(error.offset, error2.offset);
QCOMPARE(error.error, QJsonParseError::IllegalNumber); QCOMPARE(error.error, QJsonParseError::IllegalNumber);
QCOMPARE(error.offset, 14); QCOMPARE(error.offset, 14);
} }
{ {
QJsonParseError error; QJsonParseError error;
QJsonParseError error2;
QByteArray json = "[\n -1e-10000]"; QByteArray json = "[\n -1e-10000]";
QJsonValue val = QJsonValue::fromJson(json, &error); QJsonValue val = QJsonValue::fromJson(json, &error);
QJsonDocument doc = QJsonDocument::fromJson(json, &error2);
QVERIFY(val.isUndefined()); QVERIFY(val.isUndefined());
QVERIFY(doc.isEmpty());
QCOMPARE(error.error, error2.error);
QCOMPARE(error.offset, error2.offset);
QCOMPARE(error.error, QJsonParseError::IllegalNumber); QCOMPARE(error.error, QJsonParseError::IllegalNumber);
QCOMPARE(error.offset, 15); QCOMPARE(error.offset, 15);
} }
{ {
QJsonParseError error; QJsonParseError error;
QJsonParseError error2;
QByteArray json = "[\n \"\\u12\"]"; QByteArray json = "[\n \"\\u12\"]";
QJsonValue val = QJsonValue::fromJson(json, &error); QJsonValue val = QJsonValue::fromJson(json, &error);
QJsonDocument doc = QJsonDocument::fromJson(json, &error2);
QVERIFY(val.isUndefined()); QVERIFY(val.isUndefined());
QVERIFY(doc.isEmpty());
QCOMPARE(error.error, error2.error);
QCOMPARE(error.offset, error2.offset);
QCOMPARE(error.error, QJsonParseError::IllegalEscapeSequence); QCOMPARE(error.error, QJsonParseError::IllegalEscapeSequence);
QCOMPARE(error.offset, 11); QCOMPARE(error.offset, 11);
} }
{ {
QJsonParseError error; QJsonParseError error;
QJsonParseError error2;
QByteArray json = "[\n \"foo" INVALID_UNICODE "bar\"]"; QByteArray json = "[\n \"foo" INVALID_UNICODE "bar\"]";
QJsonValue val = QJsonValue::fromJson(json, &error); QJsonValue val = QJsonValue::fromJson(json, &error);
QJsonDocument doc = QJsonDocument::fromJson(json, &error2);
QVERIFY(val.isUndefined()); QVERIFY(val.isUndefined());
QVERIFY(doc.isEmpty());
QCOMPARE(error.error, error2.error);
QCOMPARE(error.offset, error2.offset);
QCOMPARE(error.error, QJsonParseError::IllegalUTF8String); QCOMPARE(error.error, QJsonParseError::IllegalUTF8String);
QCOMPARE(error.offset, 12); QCOMPARE(error.offset, 12);
} }
{ {
QJsonParseError error; QJsonParseError error;
QJsonParseError error2;
QByteArray json = "[\n \""; QByteArray json = "[\n \"";
QJsonValue val = QJsonValue::fromJson(json, &error); QJsonValue val = QJsonValue::fromJson(json, &error);
QJsonDocument doc = QJsonDocument::fromJson(json, &error2);
QVERIFY(val.isUndefined()); QVERIFY(val.isUndefined());
QVERIFY(doc.isEmpty());
QCOMPARE(error.error, error2.error);
QCOMPARE(error.offset, error2.offset);
QCOMPARE(error.error, QJsonParseError::UnterminatedString); QCOMPARE(error.error, QJsonParseError::UnterminatedString);
QCOMPARE(error.offset, 8); QCOMPARE(error.offset, 8);
} }
{ {
QJsonParseError error; QJsonParseError error;
QJsonParseError error2;
QByteArray json = "[\n \"c" UNICODE_DJE "a\\u12\"]"; QByteArray json = "[\n \"c" UNICODE_DJE "a\\u12\"]";
QJsonValue val = QJsonValue::fromJson(json, &error); QJsonValue val = QJsonValue::fromJson(json, &error);
QJsonDocument doc = QJsonDocument::fromJson(json, &error2);
QVERIFY(val.isUndefined()); QVERIFY(val.isUndefined());
QVERIFY(doc.isEmpty());
QCOMPARE(error.error, error2.error);
QCOMPARE(error.offset, error2.offset);
QCOMPARE(error.error, QJsonParseError::IllegalEscapeSequence); QCOMPARE(error.error, QJsonParseError::IllegalEscapeSequence);
QCOMPARE(error.offset, 15); QCOMPARE(error.offset, 15);
} }
{ {
QJsonParseError error; QJsonParseError error;
QJsonParseError error2;
QByteArray json = "[\n \"c" UNICODE_DJE "a" INVALID_UNICODE "bar\"]"; QByteArray json = "[\n \"c" UNICODE_DJE "a" INVALID_UNICODE "bar\"]";
QJsonValue val = QJsonValue::fromJson(json, &error); QJsonValue val = QJsonValue::fromJson(json, &error);
QJsonDocument doc = QJsonDocument::fromJson(json, &error2);
QVERIFY(val.isUndefined()); QVERIFY(val.isUndefined());
QVERIFY(doc.isEmpty());
QCOMPARE(error.error, error2.error);
QCOMPARE(error.offset, error2.offset);
QCOMPARE(error.error, QJsonParseError::IllegalUTF8String); QCOMPARE(error.error, QJsonParseError::IllegalUTF8String);
QCOMPARE(error.offset, 13); QCOMPARE(error.offset, 13);
} }
{ {
QJsonParseError error; QJsonParseError error;
QJsonParseError error2;
QByteArray json = "[\n \"c" UNICODE_DJE "a ]"; QByteArray json = "[\n \"c" UNICODE_DJE "a ]";
QJsonValue val = QJsonValue::fromJson(json, &error); QJsonValue val = QJsonValue::fromJson(json, &error);
QJsonDocument doc = QJsonDocument::fromJson(json, &error2);
QVERIFY(val.isUndefined()); QVERIFY(val.isUndefined());
QVERIFY(doc.isEmpty());
QCOMPARE(error.error, error2.error);
QCOMPARE(error.offset, error2.offset);
QCOMPARE(error.error, QJsonParseError::UnterminatedString); QCOMPARE(error.error, QJsonParseError::UnterminatedString);
QCOMPARE(error.offset, 14); QCOMPARE(error.offset, 14);
} }
@ -2468,6 +2624,11 @@ void tst_QtJson::parseNumbers()
QJsonValue val = array.at(0); QJsonValue val = array.at(0);
QCOMPARE(val.type(), QJsonValue::Double); QCOMPARE(val.type(), QJsonValue::Double);
QCOMPARE(val.toDouble(), (double)numbers[i].n); QCOMPARE(val.toDouble(), (double)numbers[i].n);
QJsonValue val2 = QJsonValue::fromJson(numbers[i].str);
QCOMPARE(val, val2);
QCOMPARE(val2.type(), QJsonValue::Double);
QCOMPARE(val2.toDouble(), (double)numbers[i].n);
} }
} }
// test number parsing // test number parsing
@ -2510,6 +2671,11 @@ void tst_QtJson::parseNumbers()
QJsonValue val = array.at(0); QJsonValue val = array.at(0);
QCOMPARE(val.type(), QJsonValue::Double); QCOMPARE(val.type(), QJsonValue::Double);
QCOMPARE(val.toDouble(), numbers[i].n); QCOMPARE(val.toDouble(), numbers[i].n);
QJsonValue val2 = QJsonValue::fromJson(numbers[i].str);
QCOMPARE(val, val2);
QCOMPARE(val2.type(), QJsonValue::Double);
QCOMPARE(val2.toDouble(), numbers[i].n);
} }
} }
QT_IGNORE_DEPRECATIONS(constexpr bool has_denorm = std::numeric_limits<double>::has_denorm == std::denorm_present;) QT_IGNORE_DEPRECATIONS(constexpr bool has_denorm = std::numeric_limits<double>::has_denorm == std::denorm_present;)
@ -2532,6 +2698,11 @@ void tst_QtJson::parseNumbers()
QJsonValue val = array.at(0); QJsonValue val = array.at(0);
QCOMPARE(val.type(), QJsonValue::Double); QCOMPARE(val.type(), QJsonValue::Double);
QCOMPARE(val.toDouble(), numbers[i].n); QCOMPARE(val.toDouble(), numbers[i].n);
QJsonValue val2 = QJsonValue::fromJson(numbers[i].str);
QCOMPARE(val, val2);
QCOMPARE(val2.type(), QJsonValue::Double);
QCOMPARE(val2.toDouble(), numbers[i].n);
} }
} else { } else {
qInfo("Skipping denormal test as this system's double type lacks support"); qInfo("Skipping denormal test as this system's double type lacks support");
@ -2570,6 +2741,14 @@ void tst_QtJson::parseStrings()
QCOMPARE(val.type(), QJsonValue::String); QCOMPARE(val.type(), QJsonValue::String);
QCOMPARE(root.toJson(), json); QCOMPARE(root.toJson(), json);
QByteArray jsonStr = "\"";
jsonStr += strings[i];
jsonStr += '\"';
QJsonValue val2 = QJsonValue::fromJson(jsonStr);
QCOMPARE(val, val2);
QCOMPARE(val2.type(), QJsonValue::String);
// TODO: QJsonValue::toJson
} }
struct Pairs { struct Pairs {
@ -2601,6 +2780,14 @@ void tst_QtJson::parseStrings()
QCOMPARE(val.type(), QJsonValue::String); QCOMPARE(val.type(), QJsonValue::String);
QCOMPARE(root.toJson(), out); QCOMPARE(root.toJson(), out);
QByteArray jsonStr = "\"";
jsonStr += pairs[i].in;
jsonStr += '\"';
QJsonValue val2 = QJsonValue::fromJson(jsonStr);
QCOMPARE(val, val2);
QCOMPARE(val2.type(), QJsonValue::String);
// TODO: QJsonValue::toJson
} }
} }
@ -2622,6 +2809,77 @@ void tst_QtJson::parseDuplicateKeys()
QCOMPARE(it.value(), QJsonValue(false)); QCOMPARE(it.value(), QJsonValue(false));
} }
void tst_QtJson::parseTopLevel_data()
{
QTest::addColumn<QByteArrayView>("input");
QTest::addColumn<QJsonValue>("result");
QTest::addRow("true") << QByteArrayView(" true ") << QJsonValue(true);
QTest::addRow("false") << QByteArrayView("false") << QJsonValue(false);
QTest::addRow("null") << QByteArrayView("null") << QJsonValue(QJsonValue::Null);
QTest::addRow("integer") << QByteArrayView(" 42 ") << QJsonValue(42);
QTest::addRow("string") << QByteArrayView(" \" a string \" ") << QJsonValue(" a string ");
QTest::addRow("garbage object after") << QByteArrayView("true{{{{", 4) << QJsonValue(true);
QTest::addRow("garbage 'e' after (true)") << QByteArrayView("truee", 4) << QJsonValue(true);
QTest::addRow("garbage 'e' after (false)") << QByteArrayView("falsee", 5) << QJsonValue(false);
QTest::addRow("garbage 'l' after (null)")
<< QByteArrayView("nulll", 4) << QJsonValue(QJsonValue::Null);
QTest::addRow("too large integer")
<< QByteArrayView("18446744073709551616") << QJsonValue(18446744073709551616.0);
QTest::addRow("too large integer (lower precision)")
<< QByteArrayView("18446744073709551616") << QJsonValue(18446744073709552000.0);
}
void tst_QtJson::parseTopLevel()
{
QFETCH(QByteArrayView, input);
QFETCH(QJsonValue, result);
QJsonParseError error;
QJsonValue val = QJsonValue::fromJson(input, &error);
QCOMPARE(error.error, QJsonParseError::NoError);
QVERIFY(!val.isUndefined());
QCOMPARE(val, result);
QCOMPARE(val.type(), result.type());
}
void tst_QtJson::parseTopLevelErrors_data()
{
QTest::addColumn<QByteArrayView>("input");
QTest::addColumn<QJsonParseError::ParseError>("parseError");
QTest::addColumn<int>("offset");
QTest::addRow("bad true") << QByteArrayView("truee") << QJsonParseError::GarbageAtEnd << 4;
QTest::addRow("bad false") << QByteArrayView("falsee") << QJsonParseError::GarbageAtEnd << 5;
QTest::addRow("bad null") << QByteArrayView("nulll") << QJsonParseError::GarbageAtEnd << 4;
QTest::addRow("duplicate number")
<< QByteArrayView(" 42 42 ") << QJsonParseError::GarbageAtEnd << 4;
QTest::addRow("unterminated string")
<< QByteArrayView(" \" a string ") << QJsonParseError::UnterminatedString << 13;
QTest::addRow("duplicate quotes")
<< QByteArrayView(" \" a \"\" ") << QJsonParseError::GarbageAtEnd << 6;
QTest::addRow("short true") << QByteArrayView("true", 3) << QJsonParseError::IllegalValue << 1;
QTest::addRow("short false") << QByteArrayView("false", 4) << QJsonParseError::IllegalValue
<< 1;
QTest::addRow("short null") << QByteArrayView("null", 3) << QJsonParseError::IllegalValue << 1;
QTest::addRow("empty string") << QByteArrayView("") << QJsonParseError::IllegalValue << 0;
QTest::addRow("only whitespace")
<< QByteArrayView(" \t \n \t\t \n \r\r \r\n ") << QJsonParseError::IllegalValue << 17;
}
void tst_QtJson::parseTopLevelErrors()
{
QFETCH(QByteArrayView, input);
QFETCH(QJsonParseError::ParseError, parseError);
QFETCH(int, offset);
QJsonParseError error;
QJsonValue val = QJsonValue::fromJson(input, &error);
QCOMPARE(error.error, parseError);
QCOMPARE(error.offset, offset);
QVERIFY(val.isUndefined());
}
void tst_QtJson::testParser() void tst_QtJson::testParser()
{ {
QFile file(testDataDir + "/test.json"); QFile file(testDataDir + "/test.json");
@ -2779,9 +3037,14 @@ void tst_QtJson::testDebugStream()
} }
} }
QT_WARNING_PUSH
QT_WARNING_DISABLE_GCC("-Wformat-security")
QT_WARNING_DISABLE_CLANG("-Wformat-security")
void tst_QtJson::parseEscapes_data() void tst_QtJson::parseEscapes_data()
{ {
QTest::addColumn<QByteArray>("json"); QTest::addColumn<QByteArray>("json");
QTest::addColumn<QByteArray>("jsonString");
QTest::addColumn<QString>("result"); QTest::addColumn<QString>("result");
auto addUnicodeRow = [](char32_t u) { auto addUnicodeRow = [](char32_t u) {
@ -2790,24 +3053,30 @@ void tst_QtJson::parseEscapes_data()
const QString result = QString::fromUcs4(&u, 1); const QString result = QString::fromUcs4(&u, 1);
for (QChar c : result) for (QChar c : result)
ptr += snprintf(ptr, std::end(buf) - ptr, "\\u%04x", c.unicode()); ptr += snprintf(ptr, std::end(buf) - ptr, "\\u%04x", c.unicode());
QTest::addRow("U+%04X", u) << "[\"" + QByteArray(buf) + "\"]" << result; QTest::addRow("U+%04X", u) << "[\"" + QByteArray(buf) + "\"]" << '"' + QByteArray(buf) + '"' << result;
}; };
char singleCharJson[] = R"(["\x"])"; char singleCharJson[] = R"(["\x"])";
char singleCharJsonStr[] = R"("\x")";
Q_ASSERT(singleCharJson[3] == 'x'); Q_ASSERT(singleCharJson[3] == 'x');
auto makeSingleCharEscape = [&singleCharJson](char c) { Q_ASSERT(singleCharJsonStr[2] == 'x');
auto makeSingleCharEscapeRow = [&](const char *format, char c, const QString &result,
auto... formatArgs) {
singleCharJson[3] = char(c); singleCharJson[3] = char(c);
return QByteArray(singleCharJson, std::size(singleCharJson) - 1); singleCharJsonStr[2] = char(c);
QByteArray json(singleCharJson, std::size(singleCharJson) - 1);
QByteArray jsonStr(singleCharJsonStr, std::size(singleCharJsonStr) - 1);
QTest::addRow(format, formatArgs...) << json << jsonStr << result;
}; };
QTest::addRow("quote") << makeSingleCharEscape('"') << "\""; makeSingleCharEscapeRow("quote", '"', "\"");
QTest::addRow("backslash") << makeSingleCharEscape('\\') << "\\"; makeSingleCharEscapeRow("backslash", '\\', "\\");
QTest::addRow("slash") << makeSingleCharEscape('/') << "/"; makeSingleCharEscapeRow("slash", '/', "/");
QTest::addRow("backspace") << makeSingleCharEscape('b') << "\b"; makeSingleCharEscapeRow("backspace", 'b', "\b");
QTest::addRow("form-feed") << makeSingleCharEscape('f') << "\f"; makeSingleCharEscapeRow("form-feed", 'f', "\f");
QTest::addRow("newline") << makeSingleCharEscape('n') << "\n"; makeSingleCharEscapeRow("newline", 'n', "\n");
QTest::addRow("carriage-return") << makeSingleCharEscape('r') << "\r"; makeSingleCharEscapeRow("carriage-return", 'r', "\r");
QTest::addRow("tab") << makeSingleCharEscape('t') << "\t"; makeSingleCharEscapeRow("tab", 't', "\t");
// we're not going to exhaustively test all Unicode possibilities // we're not going to exhaustively test all Unicode possibilities
for (char16_t c = 0; c < 0x21; ++c) for (char16_t c = 0; c < 0x21; ++c)
@ -2825,7 +3094,8 @@ void tst_QtJson::parseEscapes_data()
addUnicodeRow(U'\U0010ffff'); addUnicodeRow(U'\U0010ffff');
QTest::addRow("mojibake-utf8") QTest::addRow("mojibake-utf8")
<< QByteArrayLiteral(R"(["A\u00e4\u00C4"])") << QStringLiteral(u"A\u00e4\u00C4"); << QByteArrayLiteral(R"(["A\u00e4\u00C4"])") << QByteArrayLiteral(R"("A\u00e4\u00C4")")
<< QStringLiteral(u"A\u00e4\u00C4");
// characters for which, preceded by backslash, it is a valid (recognized) // characters for which, preceded by backslash, it is a valid (recognized)
// escape sequence (should match the above list) // escape sequence (should match the above list)
@ -2833,25 +3103,38 @@ void tst_QtJson::parseEscapes_data()
for (int i = 0; i <= 0xff; ++i) { for (int i = 0; i <= 0xff; ++i) {
if (i && strchr(validEscapes, i)) if (i && strchr(validEscapes, i))
continue; continue;
QTest::addRow("invalid-uchar-0x%02x", i) << makeSingleCharEscape(i) << QString(char16_t(i));
makeSingleCharEscapeRow("invalid-uchar-0x%02x", i, QString(char16_t(i)), i);
} }
} }
QT_WARNING_POP
void tst_QtJson::parseEscapes() void tst_QtJson::parseEscapes()
{ {
QFETCH(QByteArray, json); QFETCH(QByteArray, json);
QFETCH(QByteArray, jsonString);
QFETCH(QString, result); QFETCH(QString, result);
{ {
QJsonDocument doc = QJsonDocument::fromJson(json); QJsonDocument doc = QJsonDocument::fromJson(json);
QJsonArray array = doc.array(); QJsonArray array = doc.array();
QCOMPARE(array.first().toString(), result); QCOMPARE(array.first().toString(), result);
} }
{
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(jsonString, &error);
QVERIFY(doc.isEmpty());
QCOMPARE(error.error, QJsonParseError::IllegalValue);
QCOMPARE(error.offset, 0);
}
{ {
QJsonValue val = QJsonValue::fromJson(json); QJsonValue val = QJsonValue::fromJson(json);
QCOMPARE(val.toArray().first().toString(), result); QCOMPARE(val.toArray().first().toString(), result);
} }
{
QJsonValue val = QJsonValue::fromJson(jsonString);
QCOMPARE(val.toString(), result);
}
} }
void tst_QtJson::makeEscapes_data() void tst_QtJson::makeEscapes_data()

View File

@ -2331,7 +2331,7 @@ void tst_qmakelib::addTestFunctions(const QString &qindir)
<< "jsontext = not good\n" << "jsontext = not good\n"
"parseJson(jsontext, json): OK = 1" "parseJson(jsontext, json): OK = 1"
<< "OK = UNDEF" << "OK = UNDEF"
<< "##:2: Error parsing JSON at 1:1: illegal value" << "##:2: Error parsing JSON at 1:2: illegal value"
<< true; << true;
QTest::newRow("parseJson(): bad number of arguments") QTest::newRow("parseJson(): bad number of arguments")