shonen.hateblo.jp

やったこと,しらべたことを書く.

手抜きjson構造体をC++11かC++14辺りでざっくり書く.

ざっくり.

JSONとは

気味の悪い拡張子. javascriptをベースに設計された,軽量のデータ交換フォーマット.

詳しくは,https://www.json.org/json-ja.html

仕様の省略

仕様の全てを実装することはそんなに大変では無いものの,ブログに載せられない量になってしまいそうだったので,仕様を省略する.

実装するのはobject*1numberだけ.arrayobjectで代用が効くので実装しない.

仕様ではkeystringだが,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はまた今度.

*1:Hash.ソースコードではPairs