summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTyler Brock <tyler.brock@gmail.com>2014-06-19 14:38:00 -0400
committerTyler Brock <tyler.brock@gmail.com>2014-06-25 15:01:59 -0400
commitcdd1172ad392b8c2321e4e140b020edf8c97b7c9 (patch)
treef4588ef7f130a7ca28161154b1aeaae7d0539628
parent11ea31ab1ac1109af219f7a36fb21dcf7dbbe5da (diff)
downloadmongo-cdd1172ad392b8c2321e4e140b020edf8c97b7c9.tar.gz
SERVER-14357: Add support for top level JSON Array
-rw-r--r--src/mongo/bson/bsonobj.h6
-rw-r--r--src/mongo/db/jsobj.cpp10
-rw-r--r--src/mongo/db/json.cpp45
-rw-r--r--src/mongo/db/json.h48
-rw-r--r--src/mongo/dbtests/jsontests.cpp31
5 files changed, 121 insertions, 19 deletions
diff --git a/src/mongo/bson/bsonobj.h b/src/mongo/bson/bsonobj.h
index 18082e562ad..08055ecbb22 100644
--- a/src/mongo/bson/bsonobj.h
+++ b/src/mongo/bson/bsonobj.h
@@ -161,7 +161,11 @@ namespace mongo {
/** Properly formatted JSON string.
@param pretty if true we try to add some lf's and indentation
*/
- std::string jsonString( JsonStringFormat format = Strict, int pretty = 0 ) const;
+ std::string jsonString(
+ JsonStringFormat format = Strict,
+ int pretty = 0,
+ bool isArray = false
+ ) const;
/** note: addFields always adds _id even if not specified */
int addFields(BSONObj& from, std::set<std::string>& fields); /* returns n added */
diff --git a/src/mongo/db/jsobj.cpp b/src/mongo/db/jsobj.cpp
index ab0a1805c3f..ed3f1cf5de7 100644
--- a/src/mongo/db/jsobj.cpp
+++ b/src/mongo/db/jsobj.cpp
@@ -490,17 +490,17 @@ namespace mongo {
return digestToString( d );
}
- string BSONObj::jsonString( JsonStringFormat format, int pretty ) const {
+ string BSONObj::jsonString( JsonStringFormat format, int pretty, bool isArray ) const {
- if ( isEmpty() ) return "{}";
+ if ( isEmpty() ) return isArray ? "[]" : "{}";
StringBuilder s;
- s << "{ ";
+ s << (isArray ? "[ " : "{ ");
BSONObjIterator i(*this);
BSONElement e = i.next();
if ( !e.eoo() )
while ( 1 ) {
- s << e.jsonString( format, true, pretty?pretty+1:0 );
+ s << e.jsonString( format, !isArray, pretty?pretty+1:0 );
e = i.next();
if ( e.eoo() )
break;
@@ -514,7 +514,7 @@ namespace mongo {
s << " ";
}
}
- s << " }";
+ s << (isArray ? " ]" : " }");
return s.str();
}
diff --git a/src/mongo/db/json.cpp b/src/mongo/db/json.cpp
index ddf5bb254fd..0162fbf860c 100644
--- a/src/mongo/db/json.cpp
+++ b/src/mongo/db/json.cpp
@@ -77,8 +77,11 @@ namespace mongo {
*SINGLEQUOTE = "'",
*DOUBLEQUOTE = "\"";
- JParse::JParse(const char* str)
- : _buf(str), _input(str), _input_end(str + strlen(str)) {}
+ JParse::JParse(const StringData& str)
+ : _buf(str.rawData())
+ , _input(_buf)
+ , _input_end(_input + str.size())
+ {}
Status JParse::parseError(const StringData& msg) {
std::ostringstream ossmsg;
@@ -191,6 +194,10 @@ namespace mongo {
return Status::OK();
}
+ Status JParse::parse(BSONObjBuilder& builder) {
+ return isArray() ? array("UNUSED", builder, false) : object("UNUSED", builder, false);
+ }
+
Status JParse::object(const StringData& fieldName, BSONObjBuilder& builder, bool subObject) {
MONGO_JSON_DEBUG("fieldName: " << fieldName);
if (!readToken(LBRACE)) {
@@ -645,23 +652,30 @@ namespace mongo {
return Status::OK();
}
- Status JParse::array(const StringData& fieldName, BSONObjBuilder& builder) {
+ Status JParse::array(const StringData& fieldName, BSONObjBuilder& builder, bool subObject) {
MONGO_JSON_DEBUG("fieldName: " << fieldName);
uint32_t index(0);
if (!readToken(LBRACKET)) {
return parseError("Expecting '['");
}
- BSONObjBuilder subBuilder(builder.subarrayStart(fieldName));
+
+ BSONObjBuilder* arrayBuilder = &builder;
+ scoped_ptr<BSONObjBuilder> subObjBuilder;
+ if (subObject) {
+ subObjBuilder.reset(new BSONObjBuilder(builder.subarrayStart(fieldName)));
+ arrayBuilder = subObjBuilder.get();
+ }
+
if (!peekToken(RBRACKET)) {
do {
- Status ret = value(builder.numStr(index), subBuilder);
+ Status ret = value(builder.numStr(index), *arrayBuilder);
if (ret != Status::OK()) {
return ret;
}
index++;
} while (readToken(COMMA));
}
- subBuilder.done();
+ arrayBuilder->done();
if (!readToken(RBRACKET)) {
return parseError("Expecting ']' or ','");
}
@@ -1190,6 +1204,10 @@ namespace mongo {
return true;
}
+ bool JParse::isArray() {
+ return peekToken(LBRACKET);
+ }
+
BSONObj fromjson(const char* jsonString, int* len) {
MONGO_JSON_DEBUG("jsonString: " << jsonString);
if (jsonString[0] == '\0') {
@@ -1200,7 +1218,7 @@ namespace mongo {
BSONObjBuilder builder;
Status ret = Status::OK();
try {
- ret = jparse.object("UNUSED", builder, false);
+ ret = jparse.parse(builder);
}
catch(std::exception& e) {
std::ostringstream message;
@@ -1221,4 +1239,17 @@ namespace mongo {
return fromjson( str.c_str() );
}
+ std::string tojson(const BSONObj& obj, JsonStringFormat format, bool pretty) {
+ return obj.jsonString(format, pretty);
+ }
+
+ std::string tojson(const BSONArray& arr, JsonStringFormat format, bool pretty) {
+ return arr.jsonString(format, pretty, true);
+ }
+
+ bool isArray(const StringData& str) {
+ JParse parser(str);
+ return parser.isArray();
+ }
+
} /* namespace mongo */
diff --git a/src/mongo/db/json.h b/src/mongo/db/json.h
index 4df19f756d5..0d3d92c126d 100644
--- a/src/mongo/db/json.h
+++ b/src/mongo/db/json.h
@@ -55,13 +55,55 @@ namespace mongo {
MONGO_CLIENT_API BSONObj fromjson(const char* str, int* len=NULL);
/**
+ * Tests whether the JSON string is an Array.
+ *
+ * Useful for assigning the result of fromjson to the right object type. Either:
+ * BSONObj
+ * BSONArray
+ *
+ * @example Using the method to select the proper type.
+ * If this method returns true, the user could store the result of fromjson
+ * inside a BSONArray, rather than a BSONObj, in order to have it print as an
+ * array when passed to tojson.
+ *
+ * @param obj The JSON string to test.
+ */
+ MONGO_CLIENT_API bool isArray(const StringData& str);
+
+ /**
+ * Convert a BSONArray to a JSON string.
+ *
+ * @param arr The BSON Array.
+ * @param format The JSON format (JS, TenGen, Strict).
+ * @param pretty Enables pretty output.
+ */
+ MONGO_CLIENT_API std::string tojson(
+ const BSONArray& arr,
+ JsonStringFormat format = Strict,
+ bool pretty = false
+ );
+
+ /**
+ * Convert a BSONObj to a JSON string.
+ *
+ * @param obj The BSON Object.
+ * @param format The JSON format (JS, TenGen, Strict).
+ * @param pretty Enables pretty output.
+ */
+ MONGO_CLIENT_API std::string tojson(
+ const BSONObj& obj,
+ JsonStringFormat format = Strict,
+ bool pretty = false
+ );
+
+ /**
* Parser class. A BSONObj is constructed incrementally by passing a
* BSONObjBuilder to the recursive parsing methods. The grammar for the
* element parsed is described before each function.
*/
class JParse {
public:
- explicit JParse(const char*);
+ explicit JParse(const StringData& str);
/*
* Notation: All-uppercase symbols denote non-terminals; all other
@@ -123,6 +165,8 @@ namespace mongo {
*/
public:
Status object(const StringData& fieldName, BSONObjBuilder&, bool subObj=true);
+ Status parse(BSONObjBuilder& builder);
+ bool isArray();
private:
/* The following functions are called with the '{' and the first
@@ -196,7 +240,7 @@ namespace mongo {
* VALUE
* | VALUE , ELEMENTS
*/
- Status array(const StringData& fieldName, BSONObjBuilder&);
+ Status array(const StringData& fieldName, BSONObjBuilder&, bool subObj=true);
/*
* NOTE: Currently only Date can be preceded by the "new" keyword
diff --git a/src/mongo/dbtests/jsontests.cpp b/src/mongo/dbtests/jsontests.cpp
index c7aa8eedb8a..607e319a081 100644
--- a/src/mongo/dbtests/jsontests.cpp
+++ b/src/mongo/dbtests/jsontests.cpp
@@ -578,10 +578,10 @@ namespace JsonTests {
virtual ~Base() {}
void run() {
ASSERT( fromjson( json() ).valid() );
- assertEquals( bson(), fromjson( json() ), "mode: <default>" );
- assertEquals( bson(), fromjson( bson().jsonString( Strict ) ), "mode: strict" );
- assertEquals( bson(), fromjson( bson().jsonString( TenGen ) ), "mode: tengen" );
- assertEquals( bson(), fromjson( bson().jsonString( JS ) ), "mode: js" );
+ assertEquals( bson(), fromjson( tojson( bson() ) ), "mode: <default>" );
+ assertEquals( bson(), fromjson( tojson( bson(), Strict ) ), "mode: strict" );
+ assertEquals( bson(), fromjson( tojson( bson(), TenGen ) ), "mode: tengen" );
+ assertEquals( bson(), fromjson( tojson( bson(), JS ) ), "mode: js" );
}
protected:
virtual BSONObj bson() const = 0;
@@ -814,6 +814,27 @@ namespace JsonTests {
}
};
+ class TopLevelArrayEmpty : public Base {
+ virtual BSONObj bson() const {
+ return BSONArray();
+ }
+ virtual string json() const {
+ return "[]";
+ }
+ };
+
+ class TopLevelArray : public Base {
+ virtual BSONObj bson() const {
+ BSONArrayBuilder builder;
+ builder.append(123);
+ builder.append("abc");
+ return builder.arr();
+ }
+ virtual string json() const {
+ return "[ 123, \"abc\" ]";
+ }
+ };
+
class Array : public Base {
virtual BSONObj bson() const {
vector< int > arr;
@@ -2675,6 +2696,8 @@ namespace JsonTests {
add< FromJsonTests::Subobject >();
add< FromJsonTests::DeeplyNestedObject >();
add< FromJsonTests::ArrayEmpty >();
+ add< FromJsonTests::TopLevelArrayEmpty >();
+ add< FromJsonTests::TopLevelArray >();
add< FromJsonTests::Array >();
add< FromJsonTests::True >();
add< FromJsonTests::False >();