如何将任何值转换为 object 并使用 boost::property_tree json 添加成员

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

我有一个程序可以在必要时修改 JSON 文档。该程序必须将 child 添加到另一个值,无论它是否已经是 object。该程序的行为应如下所示:

  1. 如果键 "x" 的 object 不存在,则创建键 "x" 的 object 并将值 y 添加为 child。
  2. 如果 object 键 "x" 确实存在,将值 y 设置为 child。
  3. 如果键 "x" 存在并且是任何其他类型,删除它,使用键 "x" 创建一个 object,然后添加值 y 作为 child .

我看到了测试 属性 树值是否存在或它们是否为指定类型的方法,但是 none 测试它是否是 object 或不是 object .

这是我制作的一个简单程序来说明我的意思:

#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 );
}

输出将是:

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

你可以在输出中看到,最后一步无法转换为 JSON(当我尝试设置它时没有抛出)。

如何实现上面列表中的场景 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. Also, they don't observe any of the JSON data types.

你的计划注定失败。 属性 Tree 不是 JSON 图书馆。 属性 树可以在同一个节点上有数据和子节点。例如

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

write_json(std::cout, p);

版画

{
    "x": "hello"
}

但添加

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

失败 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

解决方法是在添加属性之前或添加属性时删除任何值:

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);

会打印Live On Coliru

更好的笔记

在清除值之前检查值是否存在似乎更有效:

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

但是由于 属性 树存储的字符串类型性质,没有区别,因为 std::string 的条件始终为真。 (同样无法通过引用检索值。)

另请注意,当设置 n.prop1 嵌套 属性 时,如果您不控制源数据,您可能还必须检查 b 是否没有值,否则它会 fail again.

假设您的对象图结构是可以合理预测的(甚至是静态的),我建议您提前完成它:

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("");
    }
}

可以用助手概括:

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

可以实现为

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);
    }
}

奖金

事实上,有了这样的助手,还可以随时创建预期的层次结构:

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);
    }
}

现在它甚至可以在没有任何预先存在的数据的情况下运行良好:

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);
}

版画

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