json库的initializer_list创建问题
-
用c++17 variant实现的json库,想实现用 {} 大括号快速构建一个嵌套的json对象,但是加入了initializerlist后出现了灾难性的构造问题,无法构造数组、对象类型,更无法构建复杂的嵌套类型。如果去掉其中一个列表初始化,只保留构建数组的初始化列表,也不能实现匹配的问题,除非在构造时手动标明类型,但这和列表初始化的初衷完全背离了
main
#include "include/json/json.h" #include <iostream> using myjson::Json; using std::cin, std::cout, std::endl; int main(int, char **) { // 数组也无法用initializer_list构造,除非将部分单参数构造变为隐式,但这本质就是错误的,可能导致一系列问题 Json json_1 { 3.141, 2, 17}; // Json json2 // { // {"pi", 3.141}, // {"happy", true}, // {"name", "Niels"}, // {"nothing", nullptr}, // { // "answer", { // {"everything", 42} // } // }, // {"list", {1, 0, 2}} // }; cout<<json_1.is_array()<<endl; cout<<json_2.is_object()<<endl; return 0; }
json.h
#pragma once #include <cstddef> #include <initializer_list> #include <stdexcept> #include <string> #include <unordered_map> #include <utility> #include <variant> #include <vector> namespace myjson { class Json; template <typename... Ts> struct overload : Ts... { using Ts::operator()...; }; template <typename... Ts> overload(Ts...) -> overload<Ts...>; using JsonArray = std::vector<Json>; using JsonObject = std::unordered_map<std::string, Json>; class Json { public: using ValueType = std::variant<std::nullptr_t, bool, int, double, std::string, JsonArray, JsonObject>; public: Json() : m_value(nullptr) {} Json(const Json &) = default; Json(Json &&) = default; ~Json() = default; // 从基本类型构造 Json(std::nullptr_t) : m_value(nullptr) {} explicit Json(bool value) : m_value(value) {} // 防止 bool 构造为 int template<typename Int, typename = std::enable_if_t<std::is_integral_v<Int> && !std::is_same_v<Int, bool>>> explicit Json(int value) noexcept : m_value(static_cast<int>(value)) {} explicit Json(double value) : m_value(value) {} explicit Json(const std::string& value) : m_value(value) {} //const char* 是否需要构造存疑 // explicit Json(const char* value) : m_value(std::string(value)) {} explicit Json(JsonArray value) : m_value(std::move(value)) {} explicit Json(JsonObject value) : m_value(std::move(value)) {} Json(std::initializer_list<Json> init){ JsonArray array; for (const auto& element : init) { array.emplace_back(element); } m_value = std::move(array); } // object initializer_list构造 // Json(std::initializer_list<std::pair<const std::string, Json>> init) { // JsonObject dict; // for (const auto& p : init) { // dict.insert(p); // } // m_value = std::move(dict); // } Json &operator=(const Json &other) = default; Json &operator=(Json &&other) = default; public: [[nodiscard]] constexpr bool is_boolean() const noexcept { return std::holds_alternative<bool>(m_value); } [[nodiscard]] constexpr bool is_integer() const noexcept { return std::holds_alternative<int>(m_value); } [[nodiscard]] constexpr bool is_null() const noexcept { return std::holds_alternative<nullptr_t>(m_value); } [[nodiscard]] constexpr bool is_double() const noexcept { return std::holds_alternative<double>(m_value); } [[nodiscard]] constexpr bool is_string() const noexcept { return std::holds_alternative<std::string>(m_value); } [[nodiscard]] constexpr bool is_array() const noexcept { return std::holds_alternative<JsonArray>(m_value); } [[nodiscard]] constexpr bool is_object() const noexcept { return std::holds_alternative<JsonObject>(m_value); } template <typename T> const T &get() const { if (!std::holds_alternative<T>(m_value)) throw std::runtime_error("type mismatch"); return std::get<T>(m_value); } template <typename T> T &get() { if (!std::holds_alternative<T>(m_value)) throw std::runtime_error("type mismatch"); return std::get<T>(m_value); } JsonArray as_array() { if (!is_array()) throw std::runtime_error("Not an array"); return get<JsonArray>(); } JsonObject as_dict() { if (!is_object()) throw std::runtime_error("Not an dict"); return get<JsonObject>(); } private: ValueType m_value; }; } // namespace myjson
-
@dustchens 可以尝试从最简单的方式先满足Json风格初始化, 然后再这个基础看看怎么调整比较适合解析。 可能是一些思路
- 可以对 initializer_list类型复用 即是数组 也是 key-value, 内部再解析到对应的类型
- size = 2 时是 kv
- size > 2 时
- 所有子元素size=2 -> kv对象
- 数组
- 或也可以增加一些类型信息辅助解析
- Vector 本质也是间接使用 initializer_list
#include <iostream> #include <initializer_list> struct Json { Json() = default; Json(std::initializer_list<Json> init) { std::cout << "initializer_list of Json with size: " << init.size() << std::endl; } Json(double value) { std::cout << "double: " << value << std::endl; } Json(int value) { std::cout << "int: " << value << std::endl; } Json(bool value) { std::cout << "bool: " << std::boolalpha << value << std::endl; } Json(std::nullptr_t value) { std::cout << "null" << std::endl; } Json(const char* value) { std::cout << "const char*: " << value << std::endl; } Json(std::string value) { std::cout << "string: " << value << std::endl; } }; int main() { Json json_1 { 3.141, 2, 17 }; std::cout << "----" << std::endl; Json json_2 { {"pi", 3.141}, {"happy", true}, {"name", "Niels"}, {"nothing", nullptr}, { "answer", { {"everything", 42} } }, {"list", {1, 0, 2}} }; return 0; }
- 可以对 initializer_list类型复用 即是数组 也是 key-value, 内部再解析到对应的类型
-
S SPeak 将这个主题转为问答主题
-
至于为什么 initializer_list 和 数组冲突, 因为数组Vector 他本质是间接 initializer_list, 并且当一个对象有 initializer_list 构造函数时 优先匹配 initializer_list
具体可以参考mcpp-stand的在线电子书的initializer_list章节: https://sunrisepeak.github.io/mcpp-standard/cpp11/09-list-initialization.html
-
@SPeak
按照这个思路,只保留一个列表初始化,用了一个辅助函数检测了每个元素是否是数组,如果是数组就检测长度是否是2,然后首元素是否是string,如果初始化列表里全部元素都符合就判定为object。这个代码运行成功了,感谢大佬Json(std::initializer_list<Json> init) { if (is_object_list(init)) { JsonObject dict; for (const auto &el : init) { auto pair = std::get_if<JsonArray>(&el.m_value); // 必须是长度为2的数组 assert(pair && pair->size() == 2); auto key = std::get_if<std::string>(&(*pair)[0].m_value); // 第一个元素必须是字符串 assert(key); dict.emplace(*key, (*pair)[1]); } m_value = std::move(dict); } else { m_value = JsonArray(init); } } bool is_object_list(std::initializer_list<Json> init) { return std::all_of(init.begin(), init.end(), [](const Json &el) { auto pair = std::get_if<JsonArray>(&el.m_value); if (!pair || pair->size() != 2) return false; return std::holds_alternative<std::string>((*pair)[0].m_value); }); }