简体   繁体   中英

How to convert any value to an object and add members with boost::property_tree json

I have a program that modifies a JSON document if necessary. The program has to add a child to another value whether or not it's an already an object. The program should behave like so:

  1. If the object with key "x" does not exist, create object with key "x" and add value y as a child.
  2. If the object with key "x" DOES exist, set value y as a child.
  3. If the key "x" exists and is ANY OTHER type, delete it, create an object with the key "x" and then add value y as a child.

I see ways to test if property tree values exist or whether they are specified types, but none to test if it's an object or not an object.

Here's a simple program I made illustrating what I mean:

#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>

#include <sstream>
#include <iostream>

const char *json = "{"
"\"object\" : { \"mighty\" : \"wind\" },"
"\"boolean\" : true"
"}";

void printTree( std::string name, boost::property_tree::ptree tree )
{
    std::cout << "Pass '" << name << "'" << std::endl;
    try
    {
        std::stringstream ss;
        boost::property_tree::write_json( ss, tree );
        std::cout << ss.str() << std::endl;
    }
    catch( std::exception &e )
    {
        std::cout << "Could not make create json: " << e.what() << std::endl;
    }
}

int main( int argc, char *argv[] )
{
    boost::property_tree::ptree tree;

    // Load it
    std::istringstream ss_json( json );
    boost::property_tree::read_json( ss_json, tree );

    // Add a value to an object that doesn't exist
    tree.put( "none.value", "hello!" );

    // Print to see
    printTree( "Nonexistent value test", tree );

    // Add a value to the object
    tree.put( "object.value", "bello!" );

    // Print this one
    printTree( "Adding value test", tree );

    // Convert boolean to an object and add a value
    tree.put( "boolean.value", "mello!" );

    // Print it
    printTree( "Converting value test", tree );
}

The output will be:

Pass 'Nonexistent value test'
{
    "object": {
        "mighty": "wind"
    },
    "boolean": "true",
    "none": {
        "value": "hello!"
    }
}

Pass 'Adding value test'
{
    "object": {
        "mighty": "wind",
        "value": "bello!"
    },
    "boolean": "true",
    "none": {
        "value": "hello!"
    }
}

Pass 'Converting value test'
Could not make create json: <unspecified file>: ptree contains data that cannot be represented in JSON format

You can see in the output, the last step fails to convert to JSON (doesn't throw when I try to set it).

How can I achieve scenario 3 in my list above?

If the key "x" exists and is ANY OTHER type, delete it, create an object with the key "x" and then add value y as a child. Also, they don't observe any of the JSON data types.

Your plan is pretty doomed. Property Tree is not a JSON library. Property Trees can have data and child nodes at the same node. Eg

ptree p;
auto& x = p.put_child("x", {});
x.put_value("hello");

write_json(std::cout, p);

Prints

{
    "x": "hello"
}

But adding

/*auto& a = */ p.put_child("x.a", {});
write_json(std::cout, p);

Fails with Live On Coliru

terminate called after throwing an instance of 'boost::wrapexcept<boost::property_tree::json_parser::json_parser_error>'
  what():  <unspecified file>: ptree contains data that cannot be represented in JSON format

A workaround would be to remove any value prior to or when adding properties:

x.put_value("");
auto& a = p.put_child("x.a", {});
a.add("prop1", 123);
a.add("prop2", "one two three");
a.add("b.prop1", "nesting");
write_json(std::cout, p);

Would print Live On Coliru

Finer notes

It might seem more efficient to check the presence of a value before clearing it:

if (x.get_value_optional<std::string>()) {
    x.put_value("");
}

But due the the stringly typed nature of Property Tree storage there's no difference as the condition will just always be true for std::string . (Similarly there's no way to retrieve a value by reference.)

Note ALSO that when setting the n.prop1 nested property, you MAY have to also check that b has no value if you don't control the source data, because otherwise it would fail again .

Assuming that your object graph structure is reasonably predictable (or even static), I'd suggest getting it over with ahead of time:

for (auto key : { "x", "x.a", "x.a.b" }) {
    if (auto child = p.get_child_optional(key)) {
        std::cout << "clearing " << key << std::endl;
        child->put_value("");
    }
}

Which can be generalized with a helper:

clear_values("x.a.b", p);

Which could be implemented as

void clear_values(ptree::path_type path, ptree& p) {
    if (path.empty())
        return;

    auto head = path.reduce();

    auto child = p.get_child_optional(head);
    if (child) {
        child->put_value("");
        clear_values(path, *child);
    }
}

Bonus

In fact with such a helper it might become opportune to also create the expected hierarchy on the fly:

void clear_values(ptree::path_type path, ptree& p, bool create = false) {
    if (path.empty())
        return;

    auto head = path.reduce();

    auto child = p.get_child_optional(head);
    if (!child && create) {
        child = p.put_child(head, {});
    }

    if (child) {
        child->put_value("");
        clear_values(path, *child, create);
    }
}

Now it would even work well without any pre-existing data:

Live On Coliru

#include <boost/property_tree/json_parser.hpp>
#include <iostream>

using boost::property_tree::ptree;

void clear_values(ptree::path_type path, ptree& p, bool create = false) {
    if (path.empty())
        return;

    auto head = path.reduce();

    auto child = p.get_child_optional(head);
    if (!child && create) {
        child = p.put_child(head, {});
    }

    if (child) {
        child->put_value("");
        clear_values(path, *child, create);
    }
}

int main() {
    ptree p;
    clear_values("x.a.b", p, true);

    auto& a = p.get_child("x.a");
    a.add("prop1", 123);
    a.add("prop2", "one two three");
    a.add("b.prop1", "nesting");
    write_json(std::cout, p);
}

Prints

{
    "x": {
        "a": {
            "b": {
                "prop1": "nesting"
            },
            "prop1": "123",
            "prop2": "one two three"
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM