1

I'm attempting to build a document that looks like this:

{ 
  "Name" : "Joe", 
  "Age" : 34, 
  "Some Array" : [ 
    { "index" : 1, "Some Var" : 12, "Flag" : false }, 
    { "index" : 2, "Some Var" : 13, "Flag" : true }, 
    { "index" : 3, "Some Var" : 14, "Flag" : false } 
    ], 
  "Status" : "Employed" 
}

and I've been somewhat successful using the streaming builders as long as I do it all in one shot like this:

using bsoncxx::builder::stream::document;
using bsoncxx::builder::stream::open_document;
using bsoncxx::builder::stream::close_document;
using bsoncxx::builder::stream::open_array;
using bsoncxx::builder::stream::close_array;
using bsoncxx::builder::stream::finalize;

bsoncxx::builder::stream::document ArrayDoc;
ArrayDoc << "Name" << "Joe"
    << "Age" << 34
    << "Some Array" << open_array << open_document
    << "index" << 1 << "Some Var" << 12 << "Flag" << false
    << close_document << open_document
    << "index" << 2 << "Some Var" << 13 << "Flag" << true
    << close_document << open_document
    << "index" << 3 << "Some Var" << 14 << "Flag" << false
    << close_document
    << close_array
    << "Status" << "Employed";

What I really need to do though, is build the subarray with a loop to fetch the various values from other data structures. When I try to break the building process into parts, I get compiler errors on the open_document

   bsoncxx::builder::stream::document ArrayDoc;
ArrayDoc << "Name" << "Joe"
    << "Age" << 34
    << "Some Array" << open_array;

for (int i = 0; i < 4; i++)
{
    ArrayDoc << open_document
        << "index" << i << "Some Var" << 12 << "Flag" << false
        << close_document;
}
ArrayDoc << close_array
    << "Status" << "Employed";

The above gets me:

error C2679: binary '<<': no operator found which takes a right-hand operand of type 'const bsoncxx::v_noabi::builder::stream::open_document_type' (or there is no acceptable conversion)

What am I doing wrong?

3 Answers3

1

Instead of using the document stream builder, I suggest using the basic builder so you can create the different parts of the document as needed. In your case, you have several key/value pairs and an embedded array of value lists. The change in your thinking is to create subdocuments and use those to build the overall document.

So if you have a two-dimensional array of data that will be presented as an array in your (final) document:

   int values[5][3] =
   {
      {1, 12, 1},
      {2, 13, 0},
      {3, 14, 1},
      {4, 15, 1},
      {5, 16, 0}
   };

then you should build this array as it's own (intermediate) document

   bsoncxx::builder::basic::array some_array;
   for (int i = 0; i < 5; i++)
   {
      auto these_values = bsoncxx::builder::basic::document{};
      these_values.append(kvp("index", values[i][0]));
      these_values.append(kvp("Some Var", values[i][1]));
      these_values.append(kvp("Flag", (values[i][2] == 1 ? bsoncxx::types::b_bool{ true } : bsoncxx::types::b_bool{ false })));
      some_array.append(these_values);
   }

Now you can use some_array as a subdocument in the overall document.

Here is a self-contained example that runs for me:

#include <string>
#include <sstream>
#include <iostream>

#include <mongocxx/client.hpp>
#include <mongocxx/instance.hpp>
#include <bsoncxx/builder/list.hpp>
#include <bsoncxx/json.hpp>
#include <bsoncxx/builder/basic/document.hpp>
#include <bsoncxx/builder/basic/helpers.hpp>
#include <bsoncxx/builder/basic/kvp.hpp>
#include <bsoncxx/builder/basic/array.hpp>
#include <bsoncxx/json.hpp>
#include <bsoncxx/string/to_string.hpp>
#include <mongocxx/exception/exception.hpp>

mongocxx::instance instance{};

int main()
{
   int values[5][3] =
   {
      {1, 12, 1},
      {2, 13, 0},
      {3, 14, 1},
      {4, 15, 1},
      {5, 16, 0}
   };

   using bsoncxx::builder::basic::kvp;
   using bsoncxx::builder::basic::make_array;
   using bsoncxx::builder::list;

   auto my_doc = bsoncxx::builder::basic::document{};
   my_doc.append(kvp("Name", "Joe"));
   my_doc.append(kvp("Age", 34));

   bsoncxx::builder::basic::array some_array;
   for (int i = 0; i < 5; i++)
   {
      auto these_values = bsoncxx::builder::basic::document{};
      these_values.append(kvp("index", values[i][0]));
      these_values.append(kvp("Some Var", values[i][1]));
      these_values.append(kvp("Flag", (values[i][2] == 1 ? bsoncxx::types::b_bool{ true } : bsoncxx::types::b_bool{ false })));
      some_array.append(these_values);
   }
   my_doc.append(kvp("Some Array", some_array));
   my_doc.append(kvp("Status", "Employed"));

   std::cout << "my_doc json: " << bsoncxx::to_json(my_doc.view()) << std::endl;
}

This gives the output

my_doc json: { "Name" : "Joe", "Age" : 34, "Some Array" : [ { "index" : 1, "Some Var" : 12, "Flag" : true }, { "index" : 2, "Some Var" : 13, "Flag" : false }, { "index" : 3, "Some Var" : 14, "Flag" : true }, { "index" : 4, "Some Var" : 15, "Flag" : true }, { "index" : 5, "Some Var" : 16, "Flag" : false } ], "Status" : "Employed" }
PeterT
  • 8,232
  • 1
  • 17
  • 38
1

Here's how I would do it with stream builder (which is A LOT faster than the basic builder) with few changes from your original code:

using bsoncxx::builder::stream::close_array;
using bsoncxx::builder::stream::close_document;
using bsoncxx::builder::stream::document;
using bsoncxx::builder::stream::finalize;
using bsoncxx::builder::stream::open_array;
using bsoncxx::builder::stream::open_document;
bsoncxx::builder::stream::document ArrayDoc;

// note: the ( ) are used to have clang-format keep Joe on the same line
ArrayDoc << "Name" << ("Joe")
         << "Age" << 34;

// building an Array here that I will then add to ArrayDoc
bsoncxx::builder::stream::array Array;
for (int i = 0; i < 4; i++)
{
    Array << open_document
          << "index" << i << "Some Var" << 12 << "Flag" << false
          << close_document;
}
ArrayDoc << "Some Array" << Array;
ArrayDoc << "Status" << ("Employed");
auto doc = ArrayDoc << finalize;
// replace std::move(doc) with doc.view() if going to use doc again
std::cout << bsoncxx::to_json(std::move(doc));

Output = { "Name" : "Joe", "Age" : 34, "Some Array" : [ { "index" : 0, "Some Var" : 12, "Flag" : false }, { "index" : 1, "Some Var" : 12, "Flag" : false }, { "index" : 2, "Some Var" : 12, "Flag" : false }, { "index" : 3, "Some Var" : 12, "Flag" : false } ], "Status" : "Employed" }

So the only new element is that bsoncxx::builder::stream::array, and it solves everything nicely. I like this approach because you can build the array somewhere else and then just create the final document in a continuous block.

Octo Poulos
  • 523
  • 6
  • 5
0

The error you got seems linked to the following (it contains a neat example):

https://github.com/mongodb/mongo-cxx-driver/blob/a6ee87e2ff23965eaddb2dc3bfc6d1e84c52a98d/docs/content/mongocxx-v3/working-with-bson.md#L135

When building an array with the stream builder, it's important to be aware that the return type of using the << operator on a stream builder is not uniform. To build an array in a loop properly, intermediate values returned by the stream builder should be stored in variables when the type changes.

So one way to achieve this would be:

auto my_col = db["your_collection_name"];

bsoncxx::builder::stream::document ArrayDoc{};
auto some_array = ArrayDoc << "Name" << "Joe"
                           << "Age" << 34
                           << "Some Array" << open_array;

for (int i = 0; i < 4; i++)
{
    some_array = some_array << open_document
                            << "index" << i << "Some Var" << 12 << "Flag" << false
                            << close_document;
}
auto after_array = some_array << close_array;

bsoncxx::document::value doc = after_array << "Status" << "Employed" << finalize;

std::cout << bsoncxx::to_json(doc.view()) << std::endl;
my_col.insert_one({ doc.view() }); // It seems to also work with: my_col.insert_one({ ArrayDoc.view() })
SPM
  • 405
  • 1
  • 5
  • 16
  • Thanks SPM, I tried your code and I ended up with an empty document. I'm not sure how to examine the intermediate results b/c some_array and after_array end up being of type bsoncxx::v_noabi::builder::stream::key_context and I'm not sure how to deal with it. – Banjolicious Mar 09 '23 at 12:10
  • Strange, the above code generates a document for me. Maybe I'm using an old version of the driver. I've updated the insert_one with ".view()": "my_col.insert_one({doc.view()})" to avoid the warning, but it should work. Try printing it with: "std::cout << bsoncxx::to_json(doc.view()) << std::endl;". I wouldn't bother in fighting the intermediate object types (key_context, stram::array_context, etc...) – SPM Mar 11 '23 at 16:21
  • I'm using Visual Studio 19 and mongocxx 3.6.5. I'll update to 3.7.x and see if it makes any difference. – Banjolicious Mar 13 '23 at 09:56