写这个解析器主要是为了看自己的 C++ 水平,毕竟 JSON 是一个同时能做到语法简单的同时考验人的编码水平的 data structure,其次是为了好玩。
好玩吗?好玩。
结构 ¶
我们使用 https://www.json.org/json-en.html 作为解析的依据。因为是个玩具,这个解析器不打算遵循任何标准。
一个 JSON 的“值” 分为六种可能,也就是
- Null:
null
, 空值 - Boolean: 布尔值
- Number: 数字
- String: 字符串
- Array: 数组
- Object: key-value 映射
其中 Array 和 Object 又会存放任何可能的 JSON 值。
由于 C++ 显然会比较难写,先来看一下 Rust 大概会怎么写。
enum JSONNode {
Null,
Bool(bool),
Number(i64),
String(String),
Array(Vec<JSONNode>),
Object(HashMap<String, JSONNode>),
}
fn parse(sv: &str) -> Result<JSONNode, String> {
let sv = sv.trim_start();
if let Some(first) = sv.chars().nth(0) {
match first {
'n' => Ok(parseNull(sv)),
't' => Ok(parseBool(sv)),
'f' => Ok(parseBool(sv)),
'"' => Ok(parseString(sv)),
'0'..'9' => Ok(parseNumber(sv)),
'{' => Ok(parseObject(sv)),
'[' => Ok(parseArray(sv)),
_ => Err(format!("unexpected token {first}"))
}
} else {
Err("expect json value".to_string())
}
}
⬆️ 好了也就是看一下而已。但是对于 C++ 我们没有好的 enum,如果要写得比较方便而不是追求性能的话就要用到虚函数和虚继承。
首先定义一个节点的可能性:
enum class NodeType {
Null,
Boolean,
Number,
String,
Array,
Object,
};
JSON 中不会出现不属于上面六类“值”,因此“值”应该是一个虚基类,包含一些纯虚函数
class Node {
public:
inline static std::unique_ptr<Node> parse(std::string_view &sv, int dep) {
assert_depth(sv, dep);
removeWhiteSpaces(sv);
switch (sv[0]) {
case 'n':
return Null::parse(sv);
case 't':
case 'f':
return Boolean::parse(sv);
case '{':
return Object::parse(sv, dep + 1); // 控制递归深度,防止溢出
case '[':
return Array::parse(sv, dep + 1); // 同上
case '"':
return String::parse(sv);
case '-':
case '0' ... '9':
return Number::parse(sv);
default:
throw getJSONParseError(sv, "any JSON value");
}
};
virtual inline NodeType getType() const noexcept = 0;
template <class T>
requires std::is_base_of_v<Node, T>
inline T &cast() noexcept {
return *(static_cast<T *>(this));
}
template <class T>
requires std::is_base_of_v<Node, T>
inline const T &cast() const noexcept {
return *(static_cast<const T *>(this));
}
virtual inline std::string dump() const {
switch (this->getType()) {
case NodeType::Null:
return this->cast<Null>().dump();
case NodeType::Boolean:
return this->cast<Boolean>().dump();
case NodeType::Number:
return this->cast<Number>().dump();
case NodeType::String:
return this->cast<String>().dump();
case NodeType::Array:
return this->cast<Array>().dump();
case NodeType::Object:
return this->cast<Object>().dump();
}
throw JSONException("unreachable: a JSON::Node has no nodetype");
};
virtual ~Node() {};
};
一个节点显然最好是独占其资源的所有权的,所以返回一个 std::unique_ptr<Node>
,帮我们免除手动释放内存的烦恼。
因为是指针,可以简单的 cast 到子类,调用子类的方法。
而一个 JSON
对象什么其它的都不存,只存放这个 std::unique_ptr<Node>
,作为一个 unique_ptr<Node>
的包装器而存在。
Parse ¶
null, boolean, number, string 的解析比较显然,略
array 和 object 有共通性,这里只解释比较复杂的 object
class Object : public Node {
using ObjectVT = std::unordered_map<std::string, JSON>;
ObjectVT value_{};
public:
Object() {};
explicit Object(ObjectVT &&val) : value_(std::move(val)) {};
Object(Object &&) = default;
Object(const Object &) = delete; // 删掉拷贝构造函数 (虽然本来就被 JSON 删了)
Object &operator=(Object &&) = default;
Object &operator=(const Object &) = delete;
inline static std::unique_ptr<Object> parse(std::string_view &sv, int dep) {
assert_depth(sv, dep);
removeWhiteSpaces(sv);
if (sv[0] != '{')
throw getJSONParseError(sv, "object start `{`");
sv.remove_prefix(1);
removeWhiteSpaces(sv);
ObjectVT val;
bool isTComma = false; // 是不是尾随逗号
while (sv[0] != '}') {
auto key = String::parse(sv); // 解析 key
if (ENABLE_DUMPLICATED_KEY_DETECT && val.contains(key->value())) {
throw getJSONParseError(
sv, ("unique key, but got dumplicated key `" + key->value() + "`")
.c_str());
} else {
removeWhiteSpaces(sv);
if (sv[0] != ':') // 解析 key :
throw getJSONParseError(sv, "object spliter `:`");
sv.remove_prefix(1);
val.insert({key->take(), JSON(Node::parse(sv, dep + 1))});
removeWhiteSpaces(sv); // 解析 key : value
switch (sv[0]) {
case '}': // 看看结束了没
sv.remove_prefix(1);
return std::make_unique<Object>(std::move(val));
case ',':
sv.remove_prefix(1);
isTComma = true;
continue;
default:
throw getJSONParseError(sv, "object spliter `,` or object end `}`");
}
}
}
if (isTComma && !ENABLE_TRAILING_COMMA)
throw getJSONParseError(sv, "next json value");
sv.remove_prefix(1);
return std::make_unique<Object>(std::move(val));
}
inline NodeType getType() const noexcept override {
return NodeType::Object;
}
inline std::string dump() const override {
std::string s = "{";
for (const auto &[key, val] : value_) {
s += String::toJSONString(key);
s += ":";
s += val->dump();
s += ",";
}
if (!value_.empty())
s.pop_back();
s += '}';
return s;
}
JSON &operator[](const std::string &s) { return value_[s]; }
const JSON &operator[](const std::string &s) const { return value_[s]; }
};
多么简单呐(赞赏)只需要用 general 的 Node::parse 解析 object 里面的任意 value,将得到的 value node 移动进自己的 unordered_map (hash map) 就写完了!
想必看完上面的代码和注释大家都懂了吧(跑路)
因此整个 json 的 parser 就非常好写了:
static JSON parse(std::string_view sv) {
auto res = Node::parse(sv, 0);
removeWhiteSpaces(sv);
if (!sv.empty()) {
throw getJSONParseError(sv, "EOF"); // 希望整个 json 只有一个 root node
}
return JSON(std::move(res));
}
为了方便使用…… ¶
现在雏形已经出来了,但是每个 JSON 必须要显式用 make_unique 构造,以至于你会看到类似这样的蠢蠢的语法:
json["some_key"] = std::make_unique<JSON::Boolean>(false);
怎么将它简化呢?
class JSON {
public:
JSON() : _uptr(std::make_unique<Null>()) {}; // 一个 JSON 默认应该是个 null
JSON(JSON &&) = default;
template <typename T>
requires std::is_base_of_v<Node, T>
explicit JSON(std::unique_ptr<T> &&uptr) : _uptr(std::move(uptr)) {} // 用unique ptr构造
JSON &operator=(JSON &&) = default;
JSON &operator=(const JSON &) = delete;
inline JSON &operator=(std::unique_ptr<Node> &&uptr) {
_uptr = std::move(uptr);
return *this;
}
template <typename T>
requires std::is_base_of_v<Node, T>
explicit JSON(T &&node) : _uptr(std::make_unique<T>(std::forward<T>(node))) {} // 用 value 的类构造
template <typename T>
requires std::is_base_of_v<Node, T>
inline JSON &operator=(T &&node) {
_uptr = std::make_unique<T>(std::forward<T>(node));
return *this;
}
explicit JSON(std::nullptr_t) : JSON() {}
inline JSON &operator=(std::nullptr_t) { // 用 nullptr 赋值
_uptr = std::make_unique<Null>();
return *this;
}
explicit JSON(bool boolean) : _uptr(std::make_unique<Boolean>(boolean)) {}
inline JSON &operator=(bool boolean) { // 用 bool 赋值
_uptr = std::make_unique<Boolean>(boolean);
return *this;
}
template <typename IntN>
requires std::numeric_limits<IntN>::is_integer
explicit JSON(IntN integer)
: _uptr(std::make_unique<Number>(static_cast<int64_t>(integer))) {}
template <typename IntN>
requires std::numeric_limits<IntN>::is_integer
inline JSON &operator=(IntN integer) { // 用看上去是 integer 的任何类型赋值
_uptr = std::make_unique<Number>(static_cast<int64_t>(integer));
return *this;
}
explicit JSON(double float_number)
: _uptr(std::make_unique<Number>(float_number)) {}
inline JSON &operator=(double float_number) { // 用 浮点数 赋值
_uptr = std::make_unique<Number>(float_number);
return *this;
}
explicit JSON(std::string str)
: _uptr(std::make_unique<String>(std::move(str))) {}
inline JSON &operator=(std::string str) { // 用 string 赋值(因为总是要复制一遍的,不妨直接用 std::string 然后 move 进去
_uptr = std::make_unique<String>(std::move(str));
return *this;
}
explicit JSON(const char *c_str) : _uptr(std::make_unique<String>(c_str)) {}
inline JSON &operator=(const char *c_str) { // 用 C 风格 string 赋值
_uptr = std::make_unique<String>(c_str);
return *this;
}
std::unique_ptr<Node> &operator->() { return _uptr; } // 重载 ->
const std::unique_ptr<Node> &operator->() const { return _uptr; }
}
多么简单呐(赞赏)
特别的,为了方便写 Array,我们写个 makeArray,用万能引用确保东西原样转发进去:
static void pushArray_(Array &) {}
template <typename T> static void pushArray_(Array &arr, T &&t) {
arr.value_.emplace_back(JSON(std::forward<T>(t)));
}
template <typename T, typename... Args>
static void pushArray_(Array &arr, T &&t, Args &&...args) {
arr.value_.emplace_back(JSON(std::forward<T>(t)));
pushArray_(arr, std::forward<Args>(args)...);
}
template <typename... Args> static Array makeArray(Args &&...args) {
Array res;
pushArray_(res, std::forward<Args>(args)...);
return res;
}
现在你可以这样 dump 一个 JSON 了
JSON json{std::make_unique<JSON::Object>()};
auto &root = json->cast<JSON::Object>();
root["123"] = 456;
root["\n\n"] = "hello";
root["\b\t"] = 114.514;
root["true"] = false;
root["null"] = nullptr;
root["array"] =
JSON::Array::makeArray(1, true, 11514.1919, -2147483648, "miao", nullptr);
root["array2"] = JSON::Array::makeArray(
1,
JSON::Array::makeArray(1, JSON::Array::makeArray(4),
JSON::Array::makeArray(JSON::Array::makeArray(5)),
JSON::Array::makeArray(1)),
4, JSON::Array::makeArray());
std::cout << json->dump();
输出(美化后)
{
"array2": [1, [1, [4], [[5]], [1]], 4, []],
"array": [1, true, 11514.1919, -2147483648, "miao", null],
"null": null,
"\b\t": 114.514,
"true": false,
"\n\n": "hello",
"123": 456
}
真是简简又单单啊
完整代码 ¶
https://github.com/Lhcfl/cpp-json-learn/blob/main/cppjson.h
684 行,实现了一个还可以的玩具 C++ 解析库,并且能 90% 情况下和 JavaScript 的 JSON 解析保持抛出一致的异常()
所以 ¶
看到这坨东西的时候 ⬇️ 还写 C++ 吗.png 还写吗
template <typename IntN>
requires std::numeric_limits<IntN>::is_integer
explicit JSON(IntN integer)
: _uptr(std::make_unique<Number>(static_cast<int64_t>(integer))) {}
template <typename IntN>
requires std::numeric_limits<IntN>::is_integer
inline JSON &operator=(IntN integer) { // 用看上去是 integer 的任何类型赋值
_uptr = std::make_unique<Number>(static_cast<int64_t>(integer));
return *this;
}
别用 C++