Creating JSON arrays in Boost using Property Trees
Asked Answered
P

7

73

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?

Profuse answered 22/1, 2010 at 1:58 Comment(0)
A
123

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"
        }
    ]
}
Airminded answered 10/10, 2012 at 14:19 Comment(5)
Please note that the numbers are encoded as strings and not numbers.Staphylorrhaphy
Hi @Luke, I am having this issue with the strings. How can I encode them as numbers?Silverts
I couldn't find a good solution when using the boost library. I switched to rapidjson instead.Staphylorrhaphy
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.Albaugh
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 howeverDhole
C
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

Crock answered 22/1, 2010 at 2:29 Comment(6)
This isn't right. After dumping to JSON, rather than getting an array, I get this: { "": "", "": "" }.Profuse
Updated the post to reflect why this is happening and how to fix it.Crock
Sad to report that it seems that it is still impossible to create arrays as root elements in 1.53.0.Prowl
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);Butts
@Gabor: Any reason why you're passing pretty=false there? It works, but the default in write_json is to pretty-print.Pavla
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.Butts
O
12

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.

Onida answered 22/1, 2010 at 9:21 Comment(2)
Lots of new libraries since I wrote this answer. Here is a nice one that I came across recently: github.com/nlohmann/jsonOnida
And Boost 1.75.0 introduced Boost JSONCorposant
B
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

Brothel answered 11/4, 2012 at 23:56 Comment(0)
C
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])
Correction answered 29/10, 2018 at 11:2 Comment(0)
F
0

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';
}
Feminacy answered 30/9, 2019 at 16:26 Comment(0)
A
0

Confused with the official document and the above answers. Below is what I understand.

Property Tree consists of nodes.
Each node is like below

 struct ptree
    {
       map<key_name,value>          data;
       vector<pair<key_name,ptree>> children; 
    };

To put 'value' into data with 'put'
To put 'node' into children with 'push_back'\

// Write 
    bt::ptree root;
    bt::ptree active;
    bt::ptree requested;
    bt::ptree n1, n2, n3;
        
    n1.put("name", "Mark");
    n1.put("age", 20);
    n1.put("job", "aaa");

    n2.put("name", "Rosie");
    n2.put("age", "19");
    n2.put("job", "bbb");

    n3.put("name", "sunwoo");
    n3.put("age", "10");
    n3.put("job", "ccc");

    active.push_back   ({ "",l1 });
    active.push_back   ({ "",l2 });
    requested.push_back({ "",l3 }); 
    root.push_back     ({"active", active});
    root.push_back     ({"requested", requested});

    bt::write_json("E:\\1.json", root);


    // READ
    bt::ptree root2;
    bt::ptree active2;
    bt::ptree requested2;
    bt::ptree r1, r2, r3;

    bt::read_json("E:\\1.json", root2);

    // loop children
    for (auto& [k,n] : root.get_child("active"))
    {       
        cout << n.get<string>("name", "unknown");
        cout << n.get<int>   ("age" , 11);
        cout << n.get<string>("job" , "man");
        cout << endl << flush;
    }
Abernon answered 30/6, 2021 at 11:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.