造轮子——C++实现一个Json库

.  前几天无意中看到我之前写的Json库,按今天的眼光去看简直稀烂2333,接口烂,实现烂,手一哆嗦就整个删掉了.

.  写C++程序的时候,有时候需要操作一些Json文件,这些Json仅作为配置使用,极少在业务逻辑里频繁操作,所以易用性是首要,性能是次要,我之前用cJSON库,无奈它是一个C风格的库,在C++代码里格格不入,即使将其封装后,看起来也十分蹩脚,我尝试写了一个比较符合C++风格的Json库,目前感觉还可以.

.  子节点由std::share_ptr管理,对Json的所有操作都被实现在Set/Get/Del函数里,从Json字符串序列化到对象则通过静态函数FromString实现,接口相当简洁.

接口清单:

Type()                          //  返回节点类型
ToInt()                         //  取int值
ToBool()                        //  取bool值
ToFloat()                       //  取float值
ToDouble()                      //  取double值
ToString()                      //  取string值
Set(val, key1, key2, ...)       //  设key...位置的节点
Get(key1, key2, ...)            //  取key...位置的节点
Del(key1, key2, ...)            //  删key...位置的节点
JValue::FromString(str)         //  从字符串解析json
JValue::FromFile(file)          //  从文件解析json
ToPrint()                       //  将节点序列化为可打印字符串

使用例子:

    //  从文件解析
    auto jfile = JValue::FromFile("a.json");
    //  从字符串解析
    auto jstring = JValue::FromString("{}");
    //  单独设值
    JValue json;
    json.Set(0);
    json.Set(true);
    json.Set("123");
    json.Set(JValue::Hash());
    json.Set(JValue::List());
    json.Set(JValue::FromString("{}"));

    //  读函数原型 JValue::Get(key1, key2, key3...)
    //  读 json["hash"]["k1"]
    json.Get("hash", "k1")->ToInt();
    //  读 json["list"][0]
    json.Get("list", 0)->ToInt();

    //  写函数原型 JValue::Set(val, key1, key2, key3...);
    //  写 json["list"] = []
    json.Set(JValue::List(), "list");
    //  写 json["list"][0] = 0
    json.Set(0, "list", 0);
    //  写 json["hash"] = {}
    json.Set(JValue::Hash(), "hash");
    //  写 json["hash"]["k"] = 0
    json.Set(0, "hash", "k");

    //  删函数原型 JValue::Del(key1, key2, key3...)
    //  删 json["hash"]["k"] = undefine
    json.Del("hash", "k");

    //  左值深拷贝
    {
        JValue json1, json2;
        json1 = json2;
    }

    //  右值拷贝
    {
        JValue json1, json2;
        json1 = std::move(json2);
    }

    //  序列化到字符串
    std::string str = json.ToPrint();

    //  范围遍历
    for (auto & jptr : json)
    { }

    //  索引遍历
    for (auto i = 0; i != json.GetCount(); ++i)
    {
        json.Get(i);
    }

    //  可结合C++标准算法
    auto it = std::find(std::begin(json), std::end(json), "key");

源码:

#pragma once

#include <string>
#include <memory>
#include <cassert>
#include <fstream>
#include <iterator>
#include <algorithm>
#include <functional>
#include <type_traits>
#include "sformat.h"

//  条件判断, 抛出异常
#define DEBUG_CHECK(exp, type, ...) if (!exp) { throw type(__VA_ARGS__); }

//  异常说明, 显示Json错误位置往后20个字符
#define DEBUG_EXPINFO(exp, string) SFormat("{0}: {1}", exp, std::string(string).substr(0, 20))

class JValue {
public:
    using JValuePtr = std::shared_ptr<JValue>;

    struct Parser {
        //  解析异常
        class Exception : public std::exception {
        public:
            Exception(const std::string & what): _what(what), std::exception(what.c_str())
            {}

        private:
            std::string _what;
        };

        static const char * SkipSpace(const char * string)
        {
            for (; *string != '\0' && *string <= 32; ++string)
                ;
            return string;
        }

        static const char * ParseList(const char * string, std::vector<JValuePtr> * output)
        {
            output->clear();
            while (*string != ']')
            {
                auto element = std::make_shared<JValue>();
                string = Parser::Parse(string, element.get());
                string = SkipSpace(string);
                *string == ',' && ++string;
                string = SkipSpace(string);
                output->push_back(element);
            }
            return ++string;
        }

        static const char * ParseHash(const char * string, std::vector<JValuePtr> * output)
        {
            output->clear();
            while (*string != '}')
            {
                auto element = std::make_shared<JValue>();
                DEBUG_CHECK((*string == '\"'), Parser::Exception, DEBUG_EXPINFO("Parse Hash Error: ", string));
                string = Parser::ParseString(string + 1, &element->_key);
                string = SkipSpace(string);
                DEBUG_CHECK((*string == ':'), Parser::Exception, DEBUG_EXPINFO("Parse Hash Error: ", string));
                string = Parser::Parse(string + 1, element.get());
                string = SkipSpace(string);
                *string == ',' && ++string;
                string = SkipSpace(string);
                output->push_back(element);
            }
            return ++string;
        }

        static const char * ParseString(const char * string, std::string * output)
        {
            output->clear();
            for (; *string != '\"'; ++string)
            {
                DEBUG_CHECK(*string != '\0', Parser::Exception, DEBUG_EXPINFO("Parse String Error", string));
                output->append(1, *string);
            }
            return string + 1;
        }

        static const char * ParseNumber(const char * string, double * output)
        {
            char value[64] = { 0 };
            for (auto i = 0; *string >= '0' && 
                             *string <= '9' || 
                             *string == '.'; ++i, ++string)
            {
                value[i] = *string;
            }
            *output = std::strtod(value, nullptr);
            return string;
        }

        static const char * ParseFalse(const char * string, double * output)
        {
            *output = 0;
            return string + 5;
        }

        static const char * ParseTrue(const char * string, double * output)
        {
            *output = 1;
            return string + 4;
        }

        static const char * Parse(const char * string, JValue * output)
        {
            string = Parser::SkipSpace(string);
            if (*string == '[')
            {
                string = ParseList(SkipSpace(string + 1), &output->_elements);
                output->_type = kLIST;
            }
            else if (*string == '{')
            {
                string = ParseHash(SkipSpace(string + 1), &output->_elements);
                output->_type = kHASH;
            }
            else if (*string == '\"')
            {
                string = ParseString(string + 1, &output->_string);
                output->_type = kSTRING;
            }
            else if (*string >= '0' && *string <= '9')
            {
                string = ParseNumber(string, &output->_number);
                output->_type = kNUMBER;
            }
            else if (string[0] == 't' && 
                     string[1] == 'r' && 
                     string[2] == 'u' && 
                     string[3] == 'e')
            {
                string = ParseTrue(string, &output->_number);
                output->_type = kBOOL;
            }
            else if (string[0] == 'f' && 
                     string[1] == 'a' && 
                     string[2] == 'l' && 
                     string[3] == 's' && 
                     string[4] == 'e')
            {
                string = ParseFalse(string, &output->_number);
                output->_type = kBOOL;
            }
            else
            {
                DEBUG_CHECK(false, Parser::Exception, DEBUG_EXPINFO("Parse Json Error", string));
            }
            return string;
        }
    };

public:
    enum JType {
        kNUMBER,
        kSTRING,
        kHASH,
        kLIST,
        kBOOL,
        kNULL,
    };

    struct Hash {};
    struct List {};

    //  如果是C++20, 可去掉这个模板
    template <class T>
    struct remove_cvref {
        using type = std::remove_cv_t<std::remove_reference_t<T>>;
    };

    template <class T>
    using IsHash = std::is_same<T, Hash>;

    template <class T>
    using IsList = std::is_same<T, List>;

    template <class T>
    using IsBool = std::is_same<T, bool>;

    template <class T>
    using IsJValue = std::is_same<T, JValue>;

    template <class T>
    using IsNull = std::is_same<T, nullptr_t>;

    template <class T>
    struct IsString: public std::false_type {};
    template <>
    struct IsString<char *>: public std::true_type {};
    template <int N>
    struct IsString<char[N]>: public std::true_type {};
    template <>
    struct IsString<std::string>: public std::true_type {};

    template <class T>
    struct IsNumber: public std::false_type {};
    template <>
    struct IsNumber<float>: public std::true_type {};
    template <>
    struct IsNumber<double>: public std::true_type {};

    template <class T>
    struct IsInteger: public std::false_type {};
    template <>
    struct IsInteger<std::int8_t>: public std::true_type {};
    template <>
    struct IsInteger<std::int16_t>: public std::true_type {};
    template <>
    struct IsInteger<std::int32_t>: public std::true_type {};
    template <>
    struct IsInteger<std::int64_t>: public std::true_type {};
    template <>
    struct IsInteger<std::uint8_t>: public std::true_type {};
    template <>
    struct IsInteger<std::uint16_t>: public std::true_type {};
    template <>
    struct IsInteger<std::uint32_t>: public std::true_type {};
    template <>
    struct IsInteger<std::uint64_t>: public std::true_type {};

    static JValue FromFile(const std::string & fname)
    {
        std::ifstream ifile(fname);
        std::string buffer;
        std::copy(std::istream_iterator<char>(ifile), 
                std::istream_iterator<char>(), 
                std::back_inserter(buffer));
        return std::move(FromString(buffer));
    }

    static JValue FromString(const std::string & string)
    {
        JValue jvalue;
        try
        {
            Parser::Parse(string.c_str(), &jvalue);
        }
        catch (const Parser::Exception &)
        {
            jvalue.Set(nullptr);
        }
        return std::move(jvalue);
    }

    JValue(): _type(JType::kNULL)
    { }

    template <class T>
    JValue(T && value)
    {
        Set(std::forward<T>(value));
    }

    JValue(JValue && json)
    {
        *this = std::move(json);
    }

    JValue & operator =(const JValue & json)
    {
        return (*this = std::move(Clone(json)));
    }

    JValue & operator =(JValue && json)
    {
        _elements = std::move(json._elements);
        _number = std::move(json._number);
        _string = std::move(json._string);
        _type = std::move(json._type);
        _key = std::move(json._key);
        json._type = kNULL;
        return *this;
    }

    JType Type() const
    {
        return _type;
    }

    const std::string & Key() const
    {
        return _key;
    }

    int ToInt() const
    {
        return static_cast<int>(_number);
    }

    float ToFloat() const
    {
        return static_cast<float>(_number);
    }

    double ToDouble() const
    {
        return static_cast<double>(_number);
    }

    bool ToBool() const
    {
        return _number != 0;
    }

    const std::string & ToString() const
    {
        return _string;
    }

    std::string ToPrint() const
    {
        std::string space;
        return std::move(Print(space));
    }

    size_t GetCount() const
    {
        return _elements.size();
    }

    std::vector<JValuePtr>::const_iterator begin() const
    {
        return _elements.begin();
    }

    std::vector<JValuePtr>::iterator begin()
    {
        return _elements.begin();
    }

    std::vector<JValuePtr>::const_iterator end() const
    {
        return _elements.end();
    }

    std::vector<JValuePtr>::iterator end()
    {
        return _elements.end();
    }

    template <class ...Keys>
    JValuePtr Get(const Keys & ...keys)
    {
        return GetImpl(keys...);
    }

    template <class ...Keys>
    bool Set(const char * val, const Keys & ...keys)
    {
        return SetImpl(val, keys...);
    }

    template <class Val, class ...Keys>
    bool Set(Val && val, const Keys & ...keys)
    {
        return SetImpl(std::forward<Val>(val), keys...);
    }

    template <class ...Keys>
    void Del(const Keys & ...keys)
    {
        DelImpl(keys...);
    }

private:
    JValue Clone(JValue && jvalue) const
    {
        return std::move(jvalue);
    }

    JValue Clone(const JValue & jvalue) const
    {
        JValue newval;
        switch (jvalue.Type())
        {
        case kNUMBER:
            {
                newval.Set(jvalue.ToDouble()); 
            }
            break;
        case kSTRING:
            {
                newval.Set(jvalue.ToString());
            }
            break;
        case kBOOL:
            {
                newval.Set(jvalue.ToBool());
            }
            break;
        case kHASH:
        case kLIST:
            {
                std::transform(std::cbegin(_elements),
                               std::cend(_elements),
                               std::back_inserter(newval._elements),
                               std::bind(&JValue::ClonePtr, this, std::placeholders::_1));
            }
            break;
        }
        newval._key = jvalue.Key();
        newval._type = jvalue.Type();
        return std::move(newval);
    }

    JValuePtr ClonePtr(const JValuePtr & ptr) const
    {
        return std::make_shared<JValue>(std::move(Clone(*ptr)));
    }

    template <class Key, class ...Keys>
    JValuePtr GetImpl(const Key & key, const Keys & ...keys)
    {
        auto jptr = GetImpl(key);
        if (jptr != nullptr)
        {
            return jptr->GetImpl(keys...);
        }
        return nullptr;
    }

    template <class Key>
    JValuePtr GetImpl(const Key & key)
    {
        if constexpr (IsInteger<Key>::value)
        {
            return (size_t)key < _elements.size()
                ? _elements.at(key)
                : nullptr;
        }
        if constexpr (IsString<Key>::value)
        {
            auto it = std::find(std::begin(_elements), std::end(_elements), key);
            return it != std::end(_elements)
                ? *it : nullptr;
        }
        return nullptr;
    }

    template <class Val, class Key, class ...Keys>
    bool SetImpl(Val && val, const Key & key, const Keys & ...keys)
    {
        auto jptr = GetImpl(key);
        if (jptr != nullptr)
        {
            return jptr->SetImpl(std::forward<Val>(val), keys...);
        }
        return true;
    }

    template <class Val, class Key>
    bool SetImpl(Val && val, const Key & key)
    {
        if constexpr (IsString<Key>::value) { assert(Type() == kHASH); }
        if constexpr (IsInteger<Key>::value) { assert(Type() == kLIST); }
        auto jptr = GetImpl(key);
        if (jptr == nullptr)
        {
            auto newval = std::make_shared<JValue>(std::forward<Val>(val));
            if constexpr (IsString<Key>::value)
            {
                newval->_key = key;
            }
            _elements.push_back(newval);
        }
        else
        {
            jptr->SetImpl(std::forward<Val>(val));
        }
        return true;
    }

    template <class Val>
    bool SetImpl(Val && val)
    {
        using Naked = remove_cvref<Val>::type;

        if constexpr (IsNumber<Naked>::value || IsInteger<Naked>::value)
        {
            _number = val;
            _elements.clear();
            _type = kNUMBER;
        }
        if constexpr (IsJValue<Naked>::value)
        {
            *this = std::move(Clone(std::forward<Val>(val)));
        }
        if constexpr (IsString<Naked>::value)
        {
            _string = std::forward<Val>(val);
            _elements.clear();
            _type = kSTRING;
        }
        if constexpr (IsBool<Naked>::value)
        {
            _number = val ? 1 : 0;
            _elements.clear();
            _type = kBOOL;
        }
        if constexpr (IsHash<Naked>::value)
        {
            _elements.clear();
            _type = kHASH;
        }
        if constexpr (IsList<Naked>::value)
        {
            _elements.clear();
            _type = kLIST;
        }
        if constexpr (IsNull<Naked>::value)
        {
            _elements.clear();
            _type = kNULL;
        }
        return true;
    }

    template <class Key, class ...Keys>
    void DelImpl(const Key & key, const Keys & ...keys)
    {
        auto jptr = GetImpl(key);
        if (jptr != nullptr)
        {
            jptr->Del(keys...);
        }
    }

    template <class Key>
    void DelImpl(const Key & key)
    {
        if constexpr (IsInteger<Key>::value)
        {
            if (key < _elements.size())
            {
                _elements.erase(std::advance(std::begin(_elements), key));
            }
        }
        if constexpr (IsString<Key>::value)
        {
            auto it = std::find(std::begin(_elements), std::end(_elements), key);
            if (it != std::end(_elements)) 
            { 
                _elements.erase(it); 
            }
        }
    }

    std::string Print(std::string & space) const
    {
        switch (Type())
        {
        case kNUMBER:
            {
                return std::to_string(_number);
            }
            break;
        case kSTRING:
            {
                return "\"" + _string + "\"";
            }
            break;
        case kHASH:
            {
                std::vector<std::string> strings;
                std::string resule("{\n");
                space.append("\t");
                resule.append(space);
                for (const auto & element : _elements)
                {
                    strings.push_back(SFormat("\"{0}\": {1}", 
                                              element->Key(), element->Print(space)));
                }
                resule.append(PrintJoin(strings, ",\n" + space));
                space.pop_back();
                resule.append("\n");
                resule.append(space);
                resule.append("}");
                return std::move(resule);
            }
            break;
        case kLIST:
            {
                std::vector<std::string> strings;
                for (const auto & element : _elements)
                {
                    strings.push_back(element->Print(space));
                }
                return "[" + PrintJoin(strings, ", ") + "]";
            }
            break;
        case kBOOL:
            {
                return ToBool() ? "\"true\"" : "\"false\"";
            }
            break;
        }
        return "null";
    }

    static std::string PrintJoin(const std::vector<std::string> & strings, const std::string & join)
    {
        size_t count = 0;
        for (const auto & string : strings)
        {
            count += string.size();
        }

        std::string resule;
        resule.reserve(count);

        if (!strings.empty())
        {
            resule.append(strings.at(0));

            for (auto i = 1; i != strings.size(); ++i)
            {
                resule.append(join);
                resule.append(strings.at(i));
            }
        }

        return resule;
    }

private:
    std::vector<JValuePtr> _elements;
    std::string _string;
    std::string _key;
    double _number;
    JType _type;

    friend struct Parser;
};

inline bool operator==(const JValue::JValuePtr & ptr, const std::string & key)
{
    return ptr->Key() == key;
}

inline bool operator!=(const JValue::JValuePtr & ptr, const std::string & key)
{
    return ptr->Key() != key;
}

从文件读取Json并打印:

auto json = std::move(JValue::FromFile("a.json"));
std::cout << json.ToPrint() << std::endl;
打印结果
a.json文件
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容