Skip to content

Instantly share code, notes, and snippets.

@ArtemGr
Last active December 21, 2023 08:52
Show Gist options
  • Save ArtemGr/2c44cb451dc6a0cb46af to your computer and use it in GitHub Desktop.
Save ArtemGr/2c44cb451dc6a0cb46af to your computer and use it in GitHub Desktop.
BSON-JSON with C++ and Rapidjson
#include <mongo/bson/bson.h>
using mongo::BSONObj; using mongo::BSONObjBuilder;
using mongo::BSONArray; using mongo::BSONArrayBuilder;
using mongo::BSONElement;
#include <rapidjson/document.h>
#include <rapidjson/reader.h>
#include <rapidjson/writer.h>
#include <rapidjson/stringbuffer.h>
// From "mongo/bson/bson_db.h".
inline BSONObjBuilder& mongo::BSONObjBuilderValueStream::operator<< (const mongo::NullLabeler& id) {
_builder->appendNull (_fieldName);
_fieldName = StringData();
return *_builder;
}
namespace BsonJson {
// Helper for `bsonToValue` (could've done it with http://en.wikipedia.org/wiki/C++14#Generic_lambdas if they would've been supported in 4.8).
template <typename T>
static inline void pushMember (rapidjson::Value& to, const char* name, T&& member, rapidjson::Value::AllocatorType& allocator) {
// NB: There's no `std::forward` because it fails to compile.
if (to.IsArray()) to.PushBack (member, allocator);
else to.AddMember (name, member, allocator);
}
// Helper for `toJson`, recursively converts the object into a rapidjson value.
static void bsonToValue (rapidjson::Value& v, const BSONObj& obj, rapidjson::Value::AllocatorType& allocator) {
using rapidjson::Value;
for (auto&& el: bsonRange (obj)) {
mongo::BSONType type = el.type(); const char* name = el.fieldName();
if (type == mongo::String) pushMember (v, name, Value (el.valuestr(), el.valuestrsize() - 1), allocator); // View.
else if (type == mongo::NumberInt) pushMember (v, name, el.Int(), allocator);
else if (type == mongo::NumberLong) pushMember (v, name, (int64_t) el.Long(), allocator);
else if (type == mongo::NumberDouble) pushMember (v, name, el.Double(), allocator);
else if (type == mongo::Bool) pushMember (v, name, el.Bool(), allocator);
else if (type == mongo::Object) {Value ov; ov.SetObject(); bsonToValue (ov, el.Obj(), allocator); pushMember (v, name, ov, allocator);}
else if (type == mongo::Array) {Value av; av.SetArray(); bsonToValue (av, el.Obj(), allocator); pushMember (v, name, av, allocator);}
else GTHROW ("Unknown type: " + std::to_string (type) + "; " + el.toString());
}
}
/// Convert BSON into string.
/// Necessary because MongoDB botched their convertor with https://jira.mongodb.org/browse/SERVER-11867. Bug 66904424.
/// @param array 1 definitely, 0 unknown, -1 object. Default is -1.
string toJson (const BSONObj& obj, int8_t array) {
char cbuf[1024]; rapidjson::MemoryPoolAllocator<> allocator (cbuf, sizeof cbuf);
rapidjson::Document doc (&allocator, 256);
if (array == 1 || (array == 0 && obj.couldBeArray())) doc.SetArray(); else doc.SetObject(); // BSONObj might be in fact an array, cf. `fromJson`.
bsonToValue (doc, obj, allocator);
rapidjson::StringBuffer buf;
rapidjson::Writer<rapidjson::StringBuffer> writer (buf, &allocator);
doc.Accept (writer);
return {buf.GetString(), buf.GetSize()};
}
static BSONObj object2bson (rapidjson::Value& value);
static BSONArray array2bson (rapidjson::Value& value);
// Helper for `fromJson`. Appends the given JSON value to the given BSON builder.
template <typename BLD> static void value2bson (rapidjson::Value& value, BLD& builder, rapidjson::Value* name = nullptr) {
if (value.IsObject()) builder << object2bson (value);
else if (value.IsArray()) builder << array2bson (value);
else if (value.IsString()) builder << mongo::StringData (value.GetString(), value.GetStringLength());
else if (value.IsInt64()) builder << (long long) value.GetInt64();
else if (value.IsInt()) builder << value.GetInt();
else if (value.IsDouble()) builder << value.GetDouble();
else if (value.IsBool()) builder << value.GetBool();
else if (value.IsNull()) builder << mongo::NullLabeler();
else GTHROW ("value2bson] Unknown JSON type: " + std::to_string (value.GetType()) + (name ? string ("; name: ") + name->GetString() : string()));
}
// Helper for `fromJson`. JSON object to BSON.
static BSONObj object2bson (rapidjson::Value& value) {
BSONObjBuilder ob;
for (auto it = value.MemberBegin(), end = value.MemberEnd(); it != end; ++it) {
auto&& obc = ob << mongo::StringData (it->name.GetString(), it->name.GetStringLength());
value2bson (it->value, obc, &it->name);
}
return ob.obj();
}
// Helper for `fromJson`. JSON array to BSON array.
static BSONArray array2bson (rapidjson::Value& value) {
BSONArrayBuilder ab;
for (auto it = value.Begin(), end = value.End(); it != end; ++it) value2bson (*it, ab);
return ab.arr();
}
/// In-situ(!) JSON parsing.
/// The returned object might be an array.
BSONObj fromJson (char* data) {
IFE0 (!data || !*data) return BSONObj();
rapidjson::MemoryPoolAllocator<> allocator; rapidjson::Document doc (&allocator); doc.ParseInsitu<0> (data);
IFE0 (doc.HasParseError()) GTHROW (string ("Can't parse JSON:") + doc.GetParseError());
if (doc.IsObject()) return object2bson (doc);
else if (doc.IsArray()) return array2bson (doc);
else GTHROW ("fromJson] JSON is not an object or array.");
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment