Last active
December 21, 2023 08:52
-
-
Save ArtemGr/2c44cb451dc6a0cb46af to your computer and use it in GitHub Desktop.
BSON-JSON with C++ and Rapidjson
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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