手抜きjson構造体をC++11かC++14辺りでざっくり書く.
ざっくり.
- 2018/6/29: ソースコード一部修正,parseを追加.説明も増やした.
JSONとは
気味の悪い拡張子. javascriptをベースに設計された,軽量のデータ交換フォーマット.
詳しくは,https://www.json.org/json-ja.html
仕様の省略
仕様の全てを実装することはそんなに大変では無いものの,ブログに載せられない量になってしまいそうだったので,仕様を省略する.
実装するのはobject
*1,number
だけ.array
もobject
で代用が効くので実装しない.
仕様ではkey
はstring
だが,number
で代用.
データ構造の実装
jsonデータ構造と,stringifyの実装.
例外処理も雑ですし,記事執筆中にメモリリークを見つけてしまったので,まだバグが残っているかもしれません.
#include "bits/stdc++.h" using namespace std; class Json { enum struct ElementType { Null, Value, Pairs }; class Element { public: Element() {} virtual ~Element() {}; virtual ostream& stringify(ostream&) const = 0; }; class Value : public Element { int value_; public: Value(int val = 0) :value_(val) {} inline int& get() { return value_; } inline int get() const { return value_; } inline bool operator==(const Value& v) const { return value_ == v.value_; } inline ostream& stringify(ostream& os) const { return os << value_; } }; class Pairs : public Element { map<int, Json> items_; // TreeMap... public: Pairs() {} inline Json& get(int key) { return items_[key]; } inline const Json& get(int key) const { auto it = items_.find(key); if (it == items_.end()) throw runtime_error("key not found"); return it->second; } inline bool has(int key) const { return items_.count(key) >= 1; } inline bool operator==(const Pairs& p) const { return items_ == p.items_; } ostream& stringify(ostream& os) const { os << '{'; bool f = false; for (const auto& item : items_) { if (f) os << ','; else f = true; os << item.first << ':'; item.second.stringify(os); } return os << '}'; } }; private: unique_ptr<Element> data_; ElementType type_; inline Value* getAsValue_() const { return static_cast<Value*>(data_.get()); } inline Pairs* getAsPairs_() const { return static_cast<Pairs*>(data_.get()); } public: Json() :type_(ElementType::Null) {} Json(const Json& j) :data_(), type_(j.type_) { if (j.type_ == ElementType::Value) data_.reset(static_cast<Element*>(new Value(*j.getAsValue_()))); if (j.type_ == ElementType::Pairs) data_.reset(static_cast<Element*>(new Pairs(*j.getAsPairs_()))); } Json(Json&& j) :data_(move(j.data_)), type_(j.type_) { } Json& operator=(Json&& j) { data_ = move(j.data_); type_ = j.type_; return *this; } inline bool isNull() const { return type_ == ElementType::Null; } inline bool isValue() const { return type_ == ElementType::Value; } inline bool isPairs() const { return type_ == ElementType::Pairs; } inline void setValue(Value&& val) { data_.reset(static_cast<Element*>(new Value(val))); type_ = ElementType::Value; } inline void setValue(int val) { data_.reset(static_cast<Element*>(new Value(val))); type_ = ElementType::Value; } inline void setValue() { data_.reset(static_cast<Element*>(new Value())); type_ = ElementType::Value; } inline void setPairs(Pairs&& pairs) { data_.reset(static_cast<Element*>(new Pairs(pairs))); type_ = ElementType::Pairs; } inline void setPairs() { data_.reset(static_cast<Element*>(new Pairs())); type_ = ElementType::Pairs; } inline void erase() { data_.release(); type_ = ElementType::Null; } inline int& operator()() { if (isNull()) setValue(); if (!isValue()) throw runtime_error("is not value"); return getAsValue_()->get(); } inline Json& operator[](int key) { if (isNull()) setPairs(); if (!isPairs()) throw runtime_error("is not pair"); return getAsPairs_()->get(key); } inline int operator()() const { if (!isValue()) throw runtime_error("is not value"); return getAsValue_()->get(); } inline const Json& operator[](int key) const { if (!isPairs()) throw runtime_error("is not pair"); return getAsPairs_()->get(key); } inline bool hasKey(int key) const { if (!isPairs()) throw runtime_error("is not pair"); return getAsPairs_()->has(key); } inline bool operator==(const Json& j) const { if (type_ != j.type_) return false; if (type_ == ElementType::Null) return true; if (type_ == ElementType::Value) return getAsValue_()->operator==(*j.getAsValue_()); if (type_ == ElementType::Pairs) return getAsPairs_()->operator==(*j.getAsPairs_()); return false; } inline ostream& stringify(ostream& os) const { if (type_ == ElementType::Value) return getAsValue_()->stringify(os); if (type_ == ElementType::Pairs) return getAsPairs_()->stringify(os); return os; } // - - - - - - - private: static inline bool isSpaceChar_(char c) { return isspace(c); } static inline bool isDigitChar_(char c) { return isdigit(c); } static int parseNumber_(istream& is) { int n = 0; int sgn = 1; while (!is.eof()) { char chr = is.get(); if (chr == '-') { if (n != 0 || sgn == -1) throw runtime_error("invalid number"); sgn = -1; } else if (!isDigitChar_(chr)) { is.unget(); return sgn*n; } n = n * 10 + int(chr - '0'); } return sgn*n; } static Pairs parsePairs_(istream& is) { Pairs pairs; int key; int state = 0; // TODO: enumにしたほうが良いよね while (!is.eof()) { char chr = is.get(); if (chr == '}') { if (state == 1) throw runtime_error("invalid ','"); if (state == 2) throw runtime_error("invalid. expected ':'"); if (state == 3) throw runtime_error("invalid ':'"); return pairs; } else if (chr == ':') { if (state == 0 || state == 1 || state == 3 || state == 4) throw runtime_error("invalid ':'"); state = 3; } else if (chr == ',') { if (state == 0) throw runtime_error("invalid ','"); if (state == 1) throw runtime_error("invalid ','"); if (state == 2) throw runtime_error("invalid. expected ':'"); if (state == 3) throw runtime_error("invalid ':'"); state = 1; } else if (isDigitChar_(chr)) { if (state == 0 || state == 1) { is.unget(); key = parseNumber_(is); state = 2; } else if (state == 2) { throw runtime_error("invalid. expected ':'"); } else if (state == 3) { is.unget(); pairs.get(key) = parse(is); state = 4; } } else if (chr == '{') { if (state == 0 || state == 1) throw runtime_error("invalid. expected key value"); if (state == 2) throw runtime_error("invalid. expected ':'"); if (state == 4) throw runtime_error("invalid. expected ','"); is.unget(); pairs.get(key) = parse(is); state = 4; } else if (isSpaceChar_(chr)) { continue; } else { throw runtime_error("invalid"); } } throw runtime_error("not found '}'"); } public: static Json parse(istream& is) { while (!is.eof()) { char chr = is.get(); if (isSpaceChar_(chr)) continue; else if (isDigitChar_(chr)) { is.unget(); Json j; j.setValue(parseNumber_(is)); return j; } else if (chr == '{') { Json j; j.setPairs(parsePairs_(is)); return j; } else { throw runtime_error("invalid"); } } return Json(); } }; void test() { Json json; json[1]() = 1; json[2]() = 2; json[3][0]() = 30; json[3][1]() = 31; json[4][1]() = 41; assert(json[1]() == 1); assert(json[3][1]() == 31); assert(json[1].isValue()); assert(json[3].isPairs()); assert(json.isPairs()); assert(json.hasKey(1)); assert(json.hasKey(3)); assert(json.hasKey(9) == false); try { json[3](); abort(); } catch (runtime_error e) { cout << "expected error : " << e.what() << endl; } try { json[1][2](); abort(); } catch (runtime_error e) { cout << "expected error : " << e.what() << endl; } json[4].erase(); json[4]() = 4; assert(json[4].isValue()); const Json constJson = json; stringstream si,so; si << "{1:1,2:2,3:{0:30,1:31},4:{}}"; Json::parse(si).stringify(so); string sii = si.str(), soi = so.str(); assert(sii == soi); } int main() { test(); cout << "test ok" << endl; return 0; }
parseJsonはまた今度.