Search code examples
c++jsonboost

Insert JSON object into an existing JSON object as a key-value-pair in Boost


I have a class hierarchy that looks like this: class A is composed of classes B and C. B and C each have a function that returns boost::json::object, which looks like this in case of B: {"B": "someValue"}

A is supposed to compose each of these objects into a structure like this:

{
  "A": {
    "B": "someValue",
    "C": "someOtherValue"
  }
}

The problem I'm facing is, that the resulting object always looks like this:

{
  "A": [
    { "B": "someValue" },
    { "C": "someOtherValue" }
  ]
}

As you can see, the objects from the subclasses are not added as key-value pairs, but as objects, each containing a single pair, into an array.

Here's a snippet to reproduce the issue:

#include <iostream>
#include <boost/json.hpp>

using namespace boost::json;

object makeObject(std::string key, int value)
{
    object obj;
    obj[key] = value;
    return obj;
}

int main() {
    object obj;
    obj["main"] = {makeObject("test1", 123), makeObject("test2", 456)};

    std::cout << obj << std::endl;
    return 0;
}

According to the Quick Look documentation of boost, I would have expected this to not create an array with single-value objects in it. What am I doing wrong here?

EDIT

I modified my example based on sehe's answer:

#include <iostream>
#include <boost/json.hpp>

auto makeProp(string_view name, value v)
{
    return object::value_type(name, std::move(v));
}

int main() {
    object obj;
    obj["main"] = {makeProp("test1", 123), makeProp("test2", 456)};

    std::cout << obj << std::endl;
    return 0;
}

But this still generates undesired output, unless I made a mistake:

{"main":[["test1",123],["test2",456]]}

To elaborate further on my usecase for the helper function, I originally intended for this to be a quick writeup to recreate the issue I described with my class hierarchy above.

I made a longer program that shows the issue in a more understandable way hopefully:

#include <boost/json.hpp>
#include <iostream>

using namespace boost::json;

class B
{
public:
    key_value_pair makeProp()
    {
        value v = {"property1", 1};
        return object::value_type("B", std::move(v));
    }
};

class C
{
public:
    key_value_pair makeProp()
    {
        value v = {{"property1", 1}, {"property2", {1, 2, 3}}};
        return object::value_type("C", std::move(v));
    }
};

class A
{
public:
    object makeProp()
    {
        B b;
        C c;

        object obj;
        obj["A"] = {b.makeProp(), c.makeProp()};
        return obj;
    }
};

int main()
{
    A a;
    std::cout << a.makeProp() << std::endl;

    return 0;
}

I hope this explains why I added the helper function, and makes it clearer what I want to achieve.

This returns:

{
  "A": [
    [
      "B",
      [
        "property1",
        1
      ]
    ],
    [
      "C",
      {
        "property1": 1,
        "property2": [
          1,
          2,
          3
        ]
      }
    ]
  ]
}

While I would have expected:

{
  "A": {
    "B": {
      "property1": 1
    },
    "C": {
      "property1": 1,
      "property2": [
        1,
        2,
        3
      ]
    }
  }
}

Solution

  • makeObject returns single objects. You pass them as the initializer list to a json::value, so you should expect {makeObject(...), makeObject(...)} to create an array: docs

    If the initializer list consists of key/value pairs, an object is created. Otherwise an array is created.

    Instead, you want to make an initializer list of key-value pair, not objects:

    #include <iostream>
    #include <boost/json/src.hpp> // for COLIRU
    
    namespace json = boost::json;
    
    int main() {
        // construct:
        json::object obj{
            {"main",
             {
                 {"test1", 123},
                 {"test2", 456},
             }},
        };
    
        std::cout << obj << std::endl;
    
        // update:
        obj["main"] = {{"B", "someValue"}, {"C", "someOtherValue"}};
        std::cout << obj << std::endl;
    }
    

    See it Live On Coliru:

    {"main":{"test1":123,"test2":456}}
    {"main":{"B":"someValue","C":"someOtherValue"}}
    

    BONUS

    If you /need/ to have the helper function for some reason:

    auto makeProp(std::string_view name, json::value v) {
        return json::object::value_type(name, std::move(v));
    }