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

Reply via email to