如何在C++中实现支持各类类数组类型及嵌套结构的JSON构造函数?
JSON解析器多形式数组初始化实现方案
问题背景
正在参考nlohmann的实现练习编写JSON解析器,希望json类支持多种数组形式的初始化:
- 初始化列表:如
json j0{1,2,"Hello",3.14,true,false,std::nullptr} - 多维原生数组:如
int nums1[][3]={{1,2,3},{4,5,6}} std::vector/std::array/std::list等序列容器- 嵌套的数组/容器/初始化列表
已给出json类的部分定义:
class json { private: /* Alias of json_value data types */ using json_array = std::vector<json_value>; // 假设已定义json_object等其他类型别名 using json_object = std::map<std::string, json_value>; union json_value { json_array* arr; json_object* obj; bool boolean; double number; std::string* str; std::nullptr_t null; }; json_value value_; // 需补充类型标记(如枚举)记录当前value_的实际类型,此处省略 public: // 待实现构造函数 };
想请教:
- 如何实现支持序列容器的构造函数?
- 如何支持任意维度的嵌套数组/容器/初始化列表?
考虑过用is_detected检查迭代器是否存在来判断序列容器,但想知道是否有更优方案;目前对任意嵌套数组的实现毫无头绪。
解决方案
1. 支持序列容器的构造函数实现
方案一:C++20 Concept(推荐)
用Concept直接约束序列容器类型,代码简洁直观,同时排除字符串类型(避免将std::string误判为数组):
#include <concepts> #include <iterator> template <typename Container> concept SequenceContainer = requires(Container c) { std::begin(c); std::end(c); !std::is_same_v<std::decay_t<Container>, std::string>; }; class json { // ... 原有成员 ... public: // 序列容器构造函数 template <SequenceContainer Container> json(const Container& container) { value_.arr = new json_array(); for (const auto& elem : container) { value_.arr->emplace_back(json(elem).value_); } // 设置类型标记,如 type_ = json_type::array; } };
方案二:SFINAE(兼容C++11/17)
如果使用C++20之前的标准,可结合std::enable_if和自定义检测traits实现:
#include <type_traits> // 检测类型是否有begin()成员 template <typename T> using has_begin_t = decltype(std::declval<T>().begin()); template <typename T> constexpr bool is_sequence_container_v = std::is_detected_v<has_begin_t, T> && !std::is_same_v<std::decay_t<T>, std::string>; class json { // ... 原有成员 ... public: template <typename Container, typename = std::enable_if_t<is_sequence_container_v<Container>>> json(const Container& container) { value_.arr = new json_array(); for (const auto& elem : container) { value_.arr->emplace_back(json(elem).value_); } // 设置类型标记 } };
2. 任意维度嵌套的实现
核心思路是递归构造:只要输入类型可转换为json(包括嵌套的容器、数组、初始化列表),就递归处理每个元素。
2.1 支持初始化列表
先实现初始化列表构造函数,天然支持嵌套结构:
class json { // ... 原有成员 ... public: // 初始化列表构造 json(std::initializer_list<json> init) { value_.arr = new json_array(); for (const auto& elem : init) { value_.arr->emplace_back(elem.value_); } // 设置类型标记 } // 基本类型构造函数(必须优先实现,避免初始化列表匹配错误) json(bool b) { value_.boolean = b; /* 设置类型标记 */ } json(double num) { value_.number = num; /* 设置类型标记 */ } json(const char* s) { value_.str = new std::string(s); /* 设置类型标记 */ } json(const std::string& s) { value_.str = new std::string(s); /* 设置类型标记 */ } json(std::nullptr_t) { value_.null = nullptr; /* 设置类型标记 */ } };
此时json j{1, "hello", {2, 3, true}}这类嵌套初始化列表可直接正常解析。
2.2 支持多维原生数组
用模板递归处理数组维度,利用std::rank判断数组是一维还是多维:
#include <type_traits> class json { // ... 原有成员 ... public: // 一维数组构造 template <typename T, std::size_t N> json(const T (&arr)[N]) { value_.arr = new json_array(); for (std::size_t i = 0; i < N; ++i) { value_.arr->emplace_back(json(arr[i]).value_); } // 设置类型标记 } // 多维数组构造(递归) template <typename T, std::size_t N, std::size_t... Rest> json(const T (&arr)[N][Rest...]) { value_.arr = new json_array(); for (std::size_t i = 0; i < N; ++i) { // 递归构造子数组对应的json对象 value_.arr->emplace_back(json(arr[i]).value_); } // 设置类型标记 } };
这样int nums[2][3] = {{1,2,3}, {4,5,6}}; json j(nums);就能正确解析为二维JSON数组。
2.3 嵌套容器的自动支持
序列容器的构造函数会遍历每个元素,若元素本身是序列容器、数组或初始化列表,会自动调用对应的json构造函数,因此天然支持嵌套,例如:
std::vector<std::list<int>> nested = {{1,2}, {3,4,5}}; json j(nested); // 自动解析为二维JSON数组
注意事项
- 内存管理:
json_value使用指针存储数组/对象/字符串,必须实现正确的析构函数、拷贝构造、移动构造等,避免内存泄漏。 - 类型标记:必须为
json类添加类型标记(如枚举json_type { array, object, boolean, number, string, null }),用于记录value_当前存储的实际类型,否则无法正确访问和析构。 - 构造函数优先级:基本类型的构造函数要优先于序列容器/数组的构造函数,避免编译器匹配错误(比如
const char*被误判为序列容器)。
内容的提问来源于stack exchange,提问作者creepy-otter
相关产品推荐
相关产品推荐

