用 C++ 写一个玩具 JSON 库

写这个解析器主要是为了看自己的 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++

Previous  Next

Loading...