json基础
这里只讲编程,所以,略。
设计节点数据的类型
json节点数据支持的类型是有标准的,所以直接枚举吧:
enum class config_value_type
{
number_integer,
number_float,
string,
array,
object,
boolean,
null,
};
那么,它们在C++中又是如何实现的呢?这也不难:
struct config_args
{
using boolean_type = bool;
using integer_type = int64_t;
using float_type = double;
template <class _CharTy, class... _Args>
using string_type = std::basic_string<_CharTy, _Args...>;
template <class _Kty, class... _Args>
using array_type = std::vector<_Kty, _Args...>;
template <class _Kty, class _Ty, class... _Args>
using object_type = std::map<_Kty, _Ty, _Args...>;
template <class _Ty>
using allocator_type = std::allocator<_Ty>;
};
把这些类型统一放在一个class里,挺干净的,不是吗?但是,却不能说挺简单的,还是需要给些说明的:
- 用using再做一次声明,多了这层抽象可以有效地屏蔽代码和真实类型之间的耦合
- 注意这里的allocator_type了吗?这主要是为了给stl的容器类型用的,因为它可以自定义分配器,这里默认也是用的stl的分配器
- 注意这里的string_type,为了最大限度的抽象,它用了一个可变参数的模板,实际上现在它仍然不是实际类型;array_type和object_type同理
注意这里没有指定_CharTy的实际类型,因为C++的字符类型现在实在是有些拥挤,不过常用的其实还是char和wchar_t,所以:
struct json_args : config_args
{
using char_type = char;
};
struct wjson_args : config_args
{
using char_type = wchar_t;
};
struct json16_args : config_args
{
using char_type = char16_t;
};
struct json32_args : config_args
{
using char_type = char32_t;
};
设计节点数据的存储结构
该怎么设计json节点数据的存储结构呢?其实也很简单,基本上,它就是一个type+一个值:
namespace detail
{
template <typename _ConfTy>
struct config_value
{
using string_type = typename _ConfTy::string_type;
using integer_type = typename _ConfTy::integer_type;
using float_type = typename _ConfTy::float_type;
using boolean_type = typename _ConfTy::boolean_type;
using array_type = typename _ConfTy::array_type;
using object_type = typename _ConfTy::object_type;
config_value_type type;
union
{
boolean_type boolean;
integer_type number_integer;
float_type number_float;
string_type* string;
object_type* object;
array_type* vector;
} data;
};
}
这里用using再一次的把类型声明了一遍,主要是为了看起来工整,其实你也可以不用using:
config_value_type type;
union
{
typename _ConfTy::boolean_type boolean;
typename _ConfTy::integer_type number_integer;
typename _ConfTy::float_type number_float;
typename _ConfTy::string_type* string;
typename _ConfTy::object_type* object;
typename _ConfTy::array_type* vector;
} data;
这里用union,毫无疑问,是为了节省存储空间。
设计节点
我们先来个只有节点数据的最简单的节点吧,啥功能都没有:
template <typename _Args>
class basic_config
{
public:
template <typename _Ty>
using allocator_type = typename _Args::template allocator_type<_Ty>;
using boolean_type = typename _Args::boolean_type;
using integer_type = typename _Args::integer_type;
using float_type = typename _Args::float_type;
using char_type = typename _Args::char_type;
using string_type =
typename _Args::template string_type<char_type, std::char_traits<char_type>, allocator_type<char_type>>;
using array_type = typename _Args::template array_type<basic_config, allocator_type<basic_config>>;
using object_type =
typename _Args::template object_type<string_type, basic_config, std::less<string_type>,
allocator_type<std::pair<const string_type, basic_config>>>;
using value_type = detail::config_value<basic_config>;
public:
value_type value_;
};
这里用using将节点数据的类型映射为真实的数据类型。
好了,终于该我们的json登场了:
using json = basic_config<json_args>;
using wjson = basic_config<wjson_args>;
你看,因为我们设计的抽象,真正的json看起来就是这么的精明干练。
测试用例
就这点东西也要写测试用例啊,听我一句用无数血泪换来的经验之谈:
一名优秀的程序员,要把写测试用例这件事形成一种习惯。
TEST_METHOD(TestBase)
{
json j;
j.value_.type = config_value_type::number_integer;
j.value_.data.number_integer = 1;
Assert::AreEqual((int64_t)1, j.value_.data.number_integer);
}
参考资料
github上的configor项目