70

I'm trying to create a JSON array using boost property trees.

The documentation says: "JSON arrays are mapped to nodes. Each element is a child node with an empty name."

So I'd like to create a property tree with empty names, then call write_json(...) to get the array out. However, the documentation doesn't tell me how to create unnamed child nodes. I tried ptree.add_child("", value), but this yields:

Assertion `!p.empty() && "Empty path not allowed for put_child."' failed

The documentation doesn't seem to address this point, at least not in any way I can figure out. Can anyone help?

2NinerRomeo
  • 2,431
  • 4
  • 22
  • 35
Chris Stucchio
  • 851
  • 1
  • 8
  • 4

6 Answers6

112

Simple Array:

#include <boost/property_tree/ptree.hpp>
using boost::property_tree::ptree;

ptree pt;
ptree children;
ptree child1, child2, child3;

child1.put("", 1);
child2.put("", 2);
child3.put("", 3);

children.push_back(std::make_pair("", child1));
children.push_back(std::make_pair("", child2));
children.push_back(std::make_pair("", child3));

pt.add_child("MyArray", children);

write_json("test1.json", pt);

results in:

{
    "MyArray":
    [
        "1",
        "2",
        "3"
    ]
}

Array over Objects:

ptree pt;
ptree children;
ptree child1, child2, child3;


child1.put("childkeyA", 1);
child1.put("childkeyB", 2);

child2.put("childkeyA", 3);
child2.put("childkeyB", 4);

child3.put("childkeyA", 5);
child3.put("childkeyB", 6);

children.push_back(std::make_pair("", child1));
children.push_back(std::make_pair("", child2));
children.push_back(std::make_pair("", child3));

pt.put("testkey", "testvalue");
pt.add_child("MyArray", children);

write_json("test2.json", pt);

results in:

{
    "testkey": "testvalue",
    "MyArray":
    [
        {
            "childkeyA": "1",
            "childkeyB": "2"
        },
        {
            "childkeyA": "3",
            "childkeyB": "4"
        },
        {
            "childkeyA": "5",
            "childkeyB": "6"
        }
    ]
}

hope this helps

Rudi Wijaya
  • 822
  • 1
  • 8
  • 21
  • 6
    Please note that the numbers are encoded as strings and not numbers. – Luke Jan 18 '16 at 02:30
  • Hi @Luke, I am having this issue with the strings. How can I encode them as numbers? – Alejandro Mar 02 '16 at 10:45
  • 3
    I couldn't find a good solution when using the boost library. I switched to rapidjson instead. – Luke Mar 04 '16 at 01:28
  • There are a bunch of restrictions with this: You cannot create arrays with just a single element, and repeating the same element multiple times doesn't work properly for me either. – dietr Apr 03 '19 at 11:19
  • There is an easier way to make pure value arrays that doesn't require an intermediate child object: children.push_back(ptree::value_type("", "1")); the value must be formatted as a string however – pcdangio Jun 27 '20 at 19:19
22

What you need to do is this piece of fun. This is from memory, but something like this works for me.

boost::property_tree::ptree root;
boost::property_tree::ptree child1;
boost::property_tree::ptree child2;

// .. fill in children here with what you want
// ...

ptree.push_back( std::make_pair("", child1 ) );
ptree.push_back( std::make_pair("", child2 ) );

But watch out there's several bugs in the json parsing and writing. Several of which I've submitted bug reports for - with no response :(

EDIT: to address concern about it serializing incorrectly as {"":"","":""}

This only happens when the array is the root element. The boost ptree writer treats all root elements as objects - never arrays or values. This is caused by the following line in boost/propert_tree/detail/json_parser_writer.hpp

else if (indent > 0 && pt.count(Str()) == pt.size())

Getting rid of the "indent > 0 &&" will allow it to write arrays correctly.

If you don't like how much space is produced you can use the patch I've provided here

Michael Anderson
  • 61,385
  • 7
  • 119
  • 164
  • This isn't right. After dumping to JSON, rather than getting an array, I get this: { "": "", "": "" }. – Chris Stucchio Jan 22 '10 at 02:56
  • Updated the post to reflect why this is happening and how to fix it. – Michael Anderson Jan 25 '10 at 22:45
  • Sad to report that it seems that it is still impossible to create arrays as root elements in 1.53.0. – conciliator Dec 03 '13 at 21:05
  • 1
    You can also try to call the internal helper direct, and fake the indent. boost::property_tree::json_parser::write_json_helper(stream, root,1,false); – Gabor Jan 30 '18 at 14:43
  • @Gabor: Any reason why you're passing `pretty=false` there? It works, but the default in `write_json` is to pretty-print. – MSalters May 07 '19 at 10:50
  • this is a 'simple' way to skip the indention code, and get skip the indent code refereed by the answer. With pretty=false the result is smaller, and it works. Because it's a long time ago, I expect that with current version it work without this workaround. – Gabor May 11 '19 at 14:43
11

When starting to use Property Tree to represent a JSON structure I encountered similar problems which I did not resolve. Also note that from the documentation, the property tree does not fully support type information:

JSON values are mapped to nodes containing the value. However, all type information is lost; numbers, as well as the literals "null", "true" and "false" are simply mapped to their string form.

After learning this, I switched to the more complete JSON implementation JSON Spirit. This library uses Boost Spirit for the JSON grammar implementation and fully supports JSON including arrays.

I suggest you use an alternative C++ JSON implementation.

Braiam
  • 4,345
  • 11
  • 47
  • 69
Yukiko
  • 1,083
  • 1
  • 8
  • 10
  • 1
    Lots of new libraries since I wrote this answer. Here is a nice one that I came across recently: https://github.com/nlohmann/json – Yukiko Nov 17 '16 at 02:38
6

In my case I wanted to add an array to a more or less arbitrary location, so, like Michael's answer, create a child tree and populate it with array elements:

using boost::property_tree::ptree;

ptree targetTree;
ptree arrayChild;
ptree arrayElement;

//add array elements as desired, loop, whatever, for example
for(int i = 0; i < 3; i++)
{
  arrayElement.put_value(i);
  arrayChild.push_back(std::make_pair("",arrayElement))
}

When the child has been populated, use the put_child() or add_child() function to add the entire child tree to the target tree, like this...

targetTree.put_child(ptree::path_type("target.path.to.array"),arrayChild)

the put_child function takes a path and a tree for an argument and will "graft" arrayChild into targetTree

2NinerRomeo
  • 2,431
  • 4
  • 22
  • 35
1

If you want JSON in C++, there's no need for Boost. With this library you can get JSON as a first class data type that behaves like an STL container.

// Create JSON on the fly.
json j2 = {
  {"pi", 3.141},
  {"happy", true},
  {"name", "Niels"},
  {"nothing", nullptr},
  {"answer", {
    {"everything", 42}
  }},
  {"list", {1, 0, 2}},
  {"object", {
    {"currency", "USD"},
    {"value", 42.99}
  }}
};

// Or treat is as an STL container; create an array using push_back
json j;
j.push_back("foo");
j.push_back(1);
j.push_back(true);

// also use emplace_back
j.emplace_back(1.78);

// iterate the array
for (json::iterator it = j.begin(); it != j.end(); ++it) {
  std::cout << *it << '\n';
}
Thane Plummer
  • 4,367
  • 2
  • 23
  • 27
0

As of boost 1.60.0, problem persists.

Offering a Python 3 workaround (Gist), which can be syscalled just after boost::property_tree::write_json.

#!/usr/bin/env python3


def lex_leaf(lf: str):
    if lf.isdecimal():
        return int(lf)
    elif lf in ['True', 'true']:
        return True
    elif lf in ['False', 'false']:
        return False
    else:
        try:
            return float(lf)
        except ValueError:
            return lf

def lex_tree(j):
    tj = type(j)
    if tj == dict:
        for k, v in j.items():
            j[k] = lex_tree(v)
    elif tj == list:
        j = [lex_tree(l) for l in j]
    elif tj == str:
        j = lex_leaf(j)
    else:
        j = lex_leaf(j)
    return j


def lex_file(fn: str):
    import json
    with open(fn, "r") as fp:
        ji = json.load(fp)
    jo = lex_tree(ji)
    with open(fn, 'w') as fp:
        json.dump(jo, fp)


if __name__ == '__main__':
    import sys
    lex_file(sys.argv[1])
Patrizio Bertoni
  • 2,172
  • 23
  • 38