json 如何使用不同类型的std::initializer_list构造函数来处理嵌套的花括号初始化器列表

mspsb9vt  于 2023-06-07  发布在  其他
关注(0)|答案(1)|浏览(201)

我正在查看nlohmann json库,我看到作者可以像这样构造json对象:

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

下面的例子中,他说:
请注意,在所有这些情况下,您永远不需要“告诉”编译器您想要使用哪个JSON值类型。如果你想明确或表达一些边缘情况,函数json::array()和json::object()会有所帮助:
我对此很感兴趣,并尝试实现我自己的这种行为的简化版本,但我没有成功。我在让初始化器列表同时接受不同类型时遇到了麻烦。我也试着分析了nlohmann库的实际源代码,我看到他的json对象也有一个构造函数,它接受一个std::initializer_list,这个std::initializer_list包含一些(据我所知)固定类型,但我不明白这是如何让std::initializer_list理解像例子中那样的嵌套的花括号初始化列表的。
判断初始化器列表是表示JSONArray还是JSONMap的条件应该如下:
如果列表中的每个嵌套元素本身都是一个长度为2的数组,其中第一个元素的类型可以用于构造JSONString。(我在考虑使用类似std::is_constructible_v<JSONString, T>的东西),而第二个元素是可以用来构造JSONObject的东西,那么我们可以推断出整个初始化器列表表示一个JSONMap,否则我们将其视为JSONAarray
最后,我想以这样的代码结束:

#include <iostream>
#include <vector>
#include <map>
#include <variant>

class JSONObject;

using JSONString = std::string;
using JSONNumber = double;
using JSONBool = bool;
using JSONNull = nullptr_t;
using JSONArray = std::vector<JSONObject>;
using JSONMap = std::map<std::string, JSONObject>;

class JSONObject {
    public:
        JSONObject() : var{JSONMap{}} {}

        template <typename T>
        JSONObject(std::initializer_list<T> list) {
            // I do not understand how to implement this
        }

    private:
        std::variant<JSONString, JSONNumber, JSONBool, JSONNull, JSONArray, JSONMap> var;
};

int main() {
    JSONObject jsonObj = {
        {"pi", 3.141},
        {"happy", true},
        {"name", "Niels"},
        {"nothing", nullptr},
        {"answer", {
            {"everything", 42}
        }},
        {"list", {1, 0, 2}},
        {"object", {
            {"currency", "USD"},
            {"value", 42.99}
        }}
    };

    return 0;
}

在做一些研究的时候,我也有了一个想法,为JSONObject做一个可变模板构造器,如下所示:

template <typename... Args> 
JSONObject(Args&&... args) {
   // some fold expression to deduce how to construct the variant
}

但即使这样,我在处理嵌套的花括号初始化列表时还是遇到了麻烦

0tdrvxhp

0tdrvxhp1#

std::initializer_list在类型上是同质的--所有元素都是相同的类型。诀窍是创建一个可以保存不同值的单一类型。这就是std::variant发挥作用的地方。
使用std::variant的棘手之处在于它不是递归的,即它不能保存自己类型的值。有一些递归的变体实现,如rva::variantBoost
另外,如果你想了解细节,here是一个关于如何实现递归变体类型的很好的教程。

更新

下面的代码是使用rva::variant作为json类型的粗略草图。原型允许使用自然的初始化语法,就像nlohmann json一样。它使用相同的技术来区分对象和数组。

示例代码

#include <iostream>
#include <map>
#include <string>
#include <vector>
#include "variant.hpp"

using std::cin, std::cout, std::endl;

using JsonBase = rva::variant<
    std::nullptr_t,
    std::string,
    double,
    bool,
    std::map<std::string, rva::self_t>,
    std::vector<rva::self_t>
    >;

class JsonValue : public JsonBase {
public:
    using JsonBase::JsonBase;
    using InitializerList = std::initializer_list<JsonValue>;

    JsonValue(InitializerList init) {
        bool is_object = std::all_of(init.begin(), init.end(), [](const auto& value) {
            if (std::holds_alternative<std::vector<JsonBase>>(value)) {
                const auto& arr = std::get<std::vector<JsonBase>>(value);
                return arr.size() == 2 and std::holds_alternative<std::string>(arr[0]);
            }
            return false;
        });
        if (is_object) {
            std::map<std::string, JsonBase> m;
            for (const auto& value : init) {
                const auto& arr = std::get<std::vector<JsonBase>>(value);
                const auto& key = std::get<std::string>(arr[0]);
                m.emplace(key, arr[1]);
            }
            *this = m;
        } else {
            std::vector<JsonBase> vec;
            for (auto&& value : init)
                vec.emplace_back(value);
            *this = vec;
        }
    }
};

std::ostream& operator<<(std::ostream& os, const JsonBase& value) {
    if (std::holds_alternative<std::nullptr_t>(value))
        os << "{ }";
    else if (std::holds_alternative<std::string>(value))
        os << "\"" << std::get<std::string>(value) << "\"";
    else if (std::holds_alternative<double>(value))
        os << std::get<double>(value);
    else if (std::holds_alternative<bool>(value))
        os << std::boolalpha << std::get<bool>(value);
    else if (std::holds_alternative<std::map<std::string, JsonBase>>(value)) {
        os << "{ ";
        for (const auto& [key, value] : std::get<std::map<std::string, JsonBase>>(value))
            os << "{ " << key << " : " << value << " } ";
        os << "]";
    }
    else if (std::holds_alternative<std::vector<JsonBase>>(value)) {
        os << "[ ";
        for (const auto& elem : std::get<std::vector<JsonBase>>(value))
            os << elem << " ";
        os << "]";
    }
    return os;
}

int main(int argc, const char *argv[]) {
    JsonValue str = "abc";
    cout << str << endl;

    JsonValue dbl = 1.0;
    cout << dbl << endl;

    JsonValue bol = true;
    cout << bol << endl;

    JsonValue vec = {
        "abc", "def", 1.0, true
    };
    cout << vec << endl;

    JsonValue m = {
        { "key0", 2.0 },
        { "key1", true }
    };
    cout << m << endl;
}

输出

"abc"
1
true
[ "abc" "def" 1 true ]
{ { key0 : 2 } { key1 : true } ]

相关问题