I would like to propose an addition to the QtJson api to allow single value
json documents (SVJ for brevity).
There have been various discussions regarding whether or not a SVJ is a valid
json document, with more recent rfc's saying yes. Here is one discussion
which also has links to others:
http://stackoverflow.com/questions/18419428/what-is-the-minimum-valid-json
Regardless of whether a SJV is a valid document, the fact remains that to be
fully interoperable with many other existing projects Qt should support them.
My initial motivation is full support for the json/jsonb column type in
Postgresql.
My proposed change would add the following methods to QJsonDocument:
bool isValue() const;
QJsonValue value() const;
void setValue(const QJsonValue &value);
The only backwards compatibility issue that i can think of would be that
fromVariant and fromJson will now return valid values documents. For that
reason it may be needed to provide new fromVariant and fromJson overloads.
I have figured out an elegant way to handle SVJ's without changing qt's binary
format. Because the binary format in it's current form can only hold a top-
level object or array, and because any old code will not be able to load a
binary document containing a single value, a version bump is required. My
idea is to have a SVJ stored as a single element json array containing the
value, but have the version set to 2. This results in no actual changes to
the binary format at all. Any non-SVJ document continues to use version 1
retaining full backwards compatibility for any document that was valid
previously.
Attached is a preliminary patch that compiles, passes the existing tests in
tests/auto/corelib/json, and adds a few new tests to exercise the new
functionality.
If it's determined that this is a reasonable approach I will work on extending
the tests and updating the documentation.
Thanks,
Matt Newell
diff --git a/src/corelib/json/qjson.cpp b/src/corelib/json/qjson.cpp
index 4b98ef0..fcf9b20 100644
--- a/src/corelib/json/qjson.cpp
+++ b/src/corelib/json/qjson.cpp
@@ -81,7 +81,7 @@ void Data::compact()
h->version = 1;
Base *b = h->root();
b->size = size;
- b->is_object = header->root()->is_object;
+ b->is_object = base->is_object;
b->length = base->length;
b->tableOffset = reserve + sizeof(Array);
@@ -131,7 +131,7 @@ void Data::compact()
bool Data::valid() const
{
- if (header->tag != QJsonDocument::BinaryFormatTag || header->version != 1u)
+ if (header->tag != QJsonDocument::BinaryFormatTag || (header->version != 1u && header->version != 2u))
return false;
bool res = false;
diff --git a/src/corelib/json/qjsondocument.cpp b/src/corelib/json/qjsondocument.cpp
index 6eca54b..4873280 100644
--- a/src/corelib/json/qjsondocument.cpp
+++ b/src/corelib/json/qjsondocument.cpp
@@ -236,7 +236,7 @@ QJsonDocument QJsonDocument::fromBinaryData(const QByteArray &data, DataValidati
memcpy(&root, data.constData() + sizeof(QJsonPrivate::Header), sizeof(QJsonPrivate::Base));
// do basic checks here, so we don't try to allocate more memory than we can.
- if (h.tag != QJsonDocument::BinaryFormatTag || h.version != 1u ||
+ if (h.tag != QJsonDocument::BinaryFormatTag || (h.version != 1u && h.version != 2u) ||
sizeof(QJsonPrivate::Header) + root.size > (uint)data.size())
return QJsonDocument();
@@ -274,6 +274,10 @@ QJsonDocument QJsonDocument::fromVariant(const QVariant &variant)
doc.setArray(QJsonArray::fromVariantList(variant.toList()));
} else if (variant.type() == QVariant::StringList) {
doc.setArray(QJsonArray::fromStringList(variant.toStringList()));
+ } else {
+ QJsonValue v = QJsonValue::fromVariant(variant);
+ if (!v.isNull())
+ doc.setValue(v);
}
return doc;
}
@@ -291,9 +295,12 @@ QVariant QJsonDocument::toVariant() const
if (!d)
return QVariant();
- if (d->header->root()->isArray())
- return QJsonArray(d, static_cast<QJsonPrivate::Array *>(d->header->root())).toVariantList();
- else
+ if (d->header->root()->isArray()) {
+ QJsonArray a(d, static_cast<QJsonPrivate::Array *>(d->header->root()));
+ if (d->header->version == 2u && a.size() == 1u)
+ return a[0].toVariant();
+ return a.toVariantList();
+ } else
return QJsonObject(d, static_cast<QJsonPrivate::Object *>(d->header->root())).toVariantMap();
}
@@ -347,9 +354,13 @@ QByteArray QJsonDocument::toJson(JsonFormat format) const
QByteArray json;
- if (d->header->root()->isArray())
- QJsonPrivate::Writer::arrayToJson(static_cast<QJsonPrivate::Array *>(d->header->root()), json, 0, (format == Compact));
- else
+ if (d->header->root()->isArray()) {
+ QJsonPrivate::Array *a = static_cast<QJsonPrivate::Array *>(d->header->root());
+ if (d->header->version == 1u)
+ QJsonPrivate::Writer::arrayToJson(a, json, 0, (format == Compact));
+ else if (d->header->version == 2u && a->length == 1u)
+ QJsonPrivate::Writer::valueToJson(a, (*a)[0], json, 0, (format == Compact));
+ } else
QJsonPrivate::Writer::objectToJson(static_cast<QJsonPrivate::Object *>(d->header->root()), json, 0, (format == Compact));
return json;
@@ -413,8 +424,8 @@ bool QJsonDocument::isArray() const
if (!d)
return false;
- QJsonPrivate::Header *h = (QJsonPrivate::Header *)d->rawData;
- return h->root()->isArray();
+ QJsonPrivate::Header *h = d->header;
+ return h->version == 1u && h->root()->isArray();
}
/*!
@@ -427,10 +438,19 @@ bool QJsonDocument::isObject() const
if (!d)
return false;
- QJsonPrivate::Header *h = (QJsonPrivate::Header *)d->rawData;
+ QJsonPrivate::Header *h = d->header;
return h->root()->isObject();
}
+
+bool QJsonDocument::isValue() const
+{
+ if (!d)
+ return false;
+
+ return d->header->version == 2u;
+}
+
/*!
Returns the QJsonObject contained in the document.
@@ -467,6 +487,19 @@ QJsonArray QJsonDocument::array() const
return QJsonArray();
}
+QJsonValue QJsonDocument::value() const
+{
+ if (d && d->header->version == 2u) {
+ QJsonPrivate::Base *b = d->header->root();
+ if (b->isArray()) {
+ QJsonArray array(d, static_cast<QJsonPrivate::Array *>(b));
+ if (array.size() == 1)
+ return array[0];
+ }
+ }
+ return QJsonValue();
+}
+
/*!
Sets \a object as the main object of this document.
@@ -521,6 +554,23 @@ void QJsonDocument::setArray(const QJsonArray &array)
d->ref.ref();
}
+void QJsonDocument::setValue(const QJsonValue &value)
+{
+ if (value.type() == QJsonValue::Array) {
+ setArray(value.toArray());
+ return;
+ }
+
+ if (value.type() == QJsonValue::Object) {
+ setObject(value.toObject());
+ return;
+ }
+
+ QJsonArray array;
+ array.push_back(value);
+ setArray(array);
+ d->header->version = 2;
+}
/*!
Returns \c true if the \a other document is equal to this document.
*/
@@ -532,6 +582,9 @@ bool QJsonDocument::operator==(const QJsonDocument &other) const
if (!d || !other.d)
return false;
+ if (d->header->version != other.d->header->version)
+ return false;
+
if (d->header->root()->isArray() != other.d->header->root()->isArray())
return false;
diff --git a/src/corelib/json/qjsondocument.h b/src/corelib/json/qjsondocument.h
index 19885a8..eb33be7 100644
--- a/src/corelib/json/qjsondocument.h
+++ b/src/corelib/json/qjsondocument.h
@@ -124,12 +124,15 @@ public:
bool isEmpty() const;
bool isArray() const;
bool isObject() const;
+ bool isValue() const;
QJsonObject object() const;
QJsonArray array() const;
+ QJsonValue value() const;
void setObject(const QJsonObject &object);
void setArray(const QJsonArray &array);
+ void setValue(const QJsonValue &value);
bool operator==(const QJsonDocument &other) const;
bool operator!=(const QJsonDocument &other) const { return !(*this == other); }
diff --git a/src/corelib/json/qjsonparser.cpp b/src/corelib/json/qjsonparser.cpp
index 6a3d1de..06a5e84 100644
--- a/src/corelib/json/qjsonparser.cpp
+++ b/src/corelib/json/qjsonparser.cpp
@@ -324,8 +324,26 @@ QJsonDocument Parser::parse(QJsonParseError *error)
if (!parseObject())
goto error;
} else {
- lastError = QJsonParseError::IllegalValue;
- goto error;
+ --json;
+ // Single values are internally stored as a 1-value array
+ // with version set to 2
+ int arrayOffset = reserveSpace(sizeof(QJsonPrivate::Array));
+
+ QJsonPrivate::Value val;
+ if (!parseValue(&val, arrayOffset))
+ goto error;
+
+ int table = arrayOffset;
+ // finalize the object
+ int tableSize = sizeof(QJsonPrivate::Value);
+ table = reserveSpace(tableSize);
+ memcpy(data + table, &val, tableSize);
+ QJsonPrivate::Array *a = (QJsonPrivate::Array *)(data + arrayOffset);
+ a->tableOffset = table - arrayOffset;
+ a->size = current - arrayOffset;
+ a->is_object = false;
+ a->length = 1;
+ h->version = 2u;
}
eatSpace();
@@ -883,7 +901,7 @@ bool Parser::parseString(bool *latin1)
}
++json;
DEBUG << "end of string";
- if (json >= end) {
+ if (json > end) {
lastError = QJsonParseError::UnterminatedString;
return false;
}
@@ -931,7 +949,7 @@ bool Parser::parseString(bool *latin1)
}
++json;
- if (json >= end) {
+ if (json > end) {
lastError = QJsonParseError::UnterminatedString;
return false;
}
diff --git a/src/corelib/json/qjsonwriter.cpp b/src/corelib/json/qjsonwriter.cpp
index b1544c7..9b3942d 100644
--- a/src/corelib/json/qjsonwriter.cpp
+++ b/src/corelib/json/qjsonwriter.cpp
@@ -120,7 +120,7 @@ static QByteArray escapedString(const QString &s)
return ba;
}
-static void valueToJson(const QJsonPrivate::Base *b, const QJsonPrivate::Value &v, QByteArray &json, int indent, bool compact)
+void Writer::valueToJson(const QJsonPrivate::Base *b, const QJsonPrivate::Value &v, QByteArray &json, int indent, bool compact)
{
QJsonValue::Type type = (QJsonValue::Type)(uint)v.type;
switch (type) {
@@ -168,7 +168,7 @@ static void arrayContentToJson(const QJsonPrivate::Array *a, QByteArray &json, i
uint i = 0;
while (1) {
json += indentString;
- valueToJson(a, a->at(i), json, indent, compact);
+ Writer::valueToJson(a, a->at(i), json, indent, compact);
if (++i == a->length) {
if (!compact)
@@ -195,7 +195,7 @@ static void objectContentToJson(const QJsonPrivate::Object *o, QByteArray &json,
json += '"';
json += escapedString(e->key());
json += compact ? "\":" : "\": ";
- valueToJson(o, e->value, json, indent, compact);
+ Writer::valueToJson(o, e->value, json, indent, compact);
if (++i == o->length) {
if (!compact)
diff --git a/src/corelib/json/qjsonwriter_p.h b/src/corelib/json/qjsonwriter_p.h
index b9cdbb6..a07e337 100644
--- a/src/corelib/json/qjsonwriter_p.h
+++ b/src/corelib/json/qjsonwriter_p.h
@@ -62,6 +62,7 @@ class Writer
public:
static void objectToJson(const QJsonPrivate::Object *o, QByteArray &json, int indent, bool compact = false);
static void arrayToJson(const QJsonPrivate::Array *a, QByteArray &json, int indent, bool compact = false);
+ static void valueToJson(const QJsonPrivate::Base *b, const QJsonPrivate::Value &v, QByteArray &json, int indent, bool compact);
};
}
diff --git a/tests/auto/corelib/json/tst_qtjson.cpp b/tests/auto/corelib/json/tst_qtjson.cpp
index 5878d56..33b7ce2 100644
--- a/tests/auto/corelib/json/tst_qtjson.cpp
+++ b/tests/auto/corelib/json/tst_qtjson.cpp
@@ -60,6 +60,7 @@ private Q_SLOTS:
void testArrayNestedEmpty();
void testArrayComfortOperators();
void testObjectNestedEmpty();
+ void testSingleValue();
void testValueRef();
void testObjectIteration();
@@ -640,6 +641,33 @@ void tst_QtJson::testObjectNestedEmpty()
QCOMPARE(reconstituted.value("inner2").type(), QJsonValue::Object);
}
+void tst_QtJson::testSingleValue()
+{
+ QJsonParseError parseError;
+ QByteArray jsonStringValue("\"a string\"");
+ QJsonDocument jDoc = QJsonDocument::fromJson(jsonStringValue,&parseError);
+ QVERIFY(parseError.error == QJsonParseError::NoError);
+ QVERIFY(jDoc.isValue());
+ QVERIFY(jDoc.value().type() == QJsonValue::String);
+ QCOMPARE(jDoc.value().toString(), QString::fromLatin1("a string"));
+ QCOMPARE(jDoc.value(),QJsonValue("a string"));
+ QCOMPARE(jDoc.toJson(), jsonStringValue);
+
+ QByteArray ba = jDoc.toBinaryData();
+ QJsonDocument jDoc2 = QJsonDocument::fromBinaryData(ba);
+ QVERIFY(jDoc2.isValue());
+ QVERIFY(jDoc2.value().type() == QJsonValue::String);
+ QCOMPARE(jDoc2.value().toString(), QString::fromLatin1("a string"));
+ QCOMPARE(jDoc2.value(),QJsonValue("a string"));
+ QVERIFY(jDoc == jDoc2);
+
+ QCOMPARE(QJsonDocument::fromVariant(QVariant(1)).value(), QJsonValue(1));
+ QCOMPARE(QJsonDocument::fromVariant(QVariant(1.234)).value(), QJsonValue(1.234));
+ QCOMPARE(QJsonDocument::fromVariant(QVariant("a string")).value(), QJsonValue("a string"));
+ QCOMPARE(QJsonDocument::fromVariant(QVariant(true)).value(), QJsonValue(true));
+ QCOMPARE(QJsonDocument::fromVariant(QVariant()).value(), QJsonValue());
+}
+
void tst_QtJson::testArrayComfortOperators()
{
QJsonArray first;
_______________________________________________
Development mailing list
[email protected]
http://lists.qt-project.org/mailman/listinfo/development