C++ JSON

JS用途很广, 当不同语言的程序之间进行通信的时候, JSON也是一种不错的格式。这里简要说说基于POCO C++的JSON实现吧。

1. JSON背景参考网站http://www.json.org/

2. JS的JSON操作建议采用json2.js

3. JSON的C++实现, 在上面的网站上有多种实现, 各有千秋, 这里介绍一种基于POCO C++的实现。

4. POCO C++的背景参考网站http://pocoproject.org/index.html, 个人认为POCO C++是完全免费开源好用的。

5. 这里主要用到POCO的两个类, Var和Token, 这里简要介绍, Var可以理解为JS中的var, 不用去考虑类型, 比如Var x = 10; x = 10.0; x = "Hi"。 Token是按照一种规则定义的标志, 即理解为按照一个规则形成的单元。 如有一个字符串"       1234Hi", 这里可以定义空白符Token, 数字Token和字符串Token

6. 长话短说, 看实现, 这里类名是CJSON, 两个对外方法

    static std::string ToString(Poco::Dynamic::Var const& var,
        Poco::Dynamic::Var const& indent);


    static Poco::Dynamic::Var Parse(std::string const& exp);


ToString是将C++对象转换成JSON格式的字符串, 比如

ToString(1, 0), ToString(true, 0), ToString("Hi", 0), ToString(1.111, 0), ToString(map1, 0), ToString(vector1, 0);

注意第二个参数是控制格式缩进的。


Parse是将JSON字符串转换成C++对象, 比如

Parse("true") == true, Parse("false") == false; Parse("null").IsEmpty(); Parse("1") == 1; Parse("\"Hi\"") == "Hi"; 


namespace Poco
{
    namespace Dynamic
    {
        typedef std::map JsObject;


        template
        class VarHolderImpl >: public VarHolder
        {
        public:
            typedef std::map ContainerType;


            VarHolderImpl(const ContainerType& val)
            {
                for (ContainerType::const_iterator j = val.begin(),
                    i = val.end(); j != i; ++j)
                {
                    Var key = j->first;
                    _val.insert(std::make_pair(key, j->second) );
                }
            }


            VarHolder* clone() const
            {
                return new VarHolderImpl(_val);
            }


            const std::type_info& type() const
            {
                return typeid(JsObject);
            }


            void convert(int& val) const
            {
                val = PtrToInt(&_val);
            }


        private:
            JsObject _val;
        };


        template <>
        class VarHolderImpl: public VarHolder
        {
        public:
            VarHolderImpl(const JsObject& val) : _val(val)
            {
            }


            VarHolder* clone() const
            {
                return new VarHolderImpl(_val);
            }


            const std::type_info& type() const
            {
                return typeid(JsObject);
            }


            void convert(int& val) const
            {
                val = PtrToInt(&_val);
            }


        private:
            JsObject _val;
        };
    }
}


class CJSON 
{
public:
    static std::string ToString(Poco::Dynamic::Var const& var,
        Poco::Dynamic::Var const& indent);


    static Poco::Dynamic::Var Parse(std::string const& exp);


private:
    static std::string ToString(Poco::Dynamic::Var const& var,
        std::string& gap, std::string& indent);


    static std::string Quote(std::string const& str);


    class CUserToken : public Poco::Token
    {
    public:
        Poco::Dynamic::Var const& Var() const { return _var; }


        Poco::Token::Class tokenClass() const
        {
            return Poco::Token::USER_TOKEN;
        }


    protected:
        Poco::Dynamic::Var _var;
    };


    class CNumberToken : public CUserToken
    {
    public:
        bool start(char c, std::istream& /*istr*/)
        {
            bool ok = c >= '0' && c <= '9' || '-' == c || '+' == c || '.' == c;
            if (ok && c != '+') { _value = c; }
            return ok;
        }


        void finish(std::istream& istr)
        {
            int c = istr.peek();
            for (; c >= '0' && c <= '9';
                istr.get(), c = istr.peek() )
            {
                _value += (char)c;
            }
            if ('.' == c)
            {
                _value += (char)c;
                for (istr.get(), c = istr.peek(); c >= '0' && c <= '9';
                    istr.get(), c = istr.peek() )
                {
                    _value += (char)c;
                }
            }
            if ('e' == c || 'E' == c)
            {
                _value += (char)c;
                istr.get();
                c = istr.peek();
                if ('-' == c || '+' == c) {
                    _value += (char)c;
                    istr.get();
                    c = istr.peek();
                }
                for (; c >= '0' && c <= '9'; istr.get(), c = istr.peek())
                {
                    _value += (char)c;
                }
            }


            int iVal;
            Poco::Int64 i64Val;
            unsigned int uiVal;
            Poco::UInt64 ui64Val;
            double dbVal;
            if (Poco::NumberParser::tryParse(_value, iVal) )
            {
                _var = iVal;
            }
            else if (Poco::NumberParser::tryParse64(_value, i64Val) )
            {
                _var = iVal;
            }
            else if (Poco::NumberParser::tryParseUnsigned(_value, uiVal) )
            {
                _var = uiVal;
            }
            else if (Poco::NumberParser::tryParseUnsigned64(_value, ui64Val) )
            {
                _var = ui64Val;
            }
            else if (Poco::NumberParser::tryParseFloat(_value, dbVal) )
            {
                _var = dbVal;
            }
            else
            {
                throw Poco::SyntaxException("Bad number");
            }
        }
    };


    class CStringToken : public CUserToken
    {
    public:
        bool start(char c, std::istream& /*istr*/)
        {
            std::string empty;
            _value.swap(empty);
            return '"' == c;
        }


        void finish(std::istream& istr)
        {
            int end = 0;
            for (int c = istr.peek(); c != -1 && !end;
                istr.get(), c = istr.peek() )
            {
                if ('"' == c) { end = 1; }
                else if ('\\' == c)
                {
                    istr.get();
                    c = istr.peek();
                    switch (c)
                    {
                    case 'u':
                        {
                            std::string hex(4, 0);
                            istr.get( (char*)hex.c_str(), hex.length() );
                            unsigned hexN = Poco::NumberParser::parseHex(hex);
                            _value += std::string( (char*)&hexN,
                                sizeof(unsigned short) );
                            break;
                        }
                    case '"':
                    case '\\':
                    case '/':
                        {
                            _value += (char)c;
                            break;
                        }
                    case 'b':
                        {
                            _value += '\b';
                            break;
                        }
                    case 'f':
                        {
                            _value += '\f';
                            break;
                        }
                    case 'n':
                        {
                            _value += '\n';
                            break;
                        }
                    case 'r':
                        {
                            _value += '\r';
                            break;
                        }
                    case 't':
                        {
                            _value += '\t';
                            break;
                        }
                    default:
                        {
                            end = 2;
                            break;
                        }
                    }
                }
                else
                {
                    _value += (char)c;
                }
            }


            if (2 == end)
            {
                throw Poco::SyntaxException("Bad string");
            }


            _var = _value;
        }
    };


    class CWordToken : public CUserToken
    {
    public:
        bool start(char c, std::istream& /*istr*/)
        {
            bool ok = 't' == c || 'f' == c || 'n' == c;
            if (ok) { _value = c; }
            return ok;
        }


        void finish(std::istream& istr)
        {
            char lead = _value[0];
            bool ok = false;
            switch (lead)
            {
            case 't':
                {
                    int c = istr.peek();
                    ok = 'r' == c;
                    if (!ok) { break; }
                    istr.get();
                    c = istr.peek();
                    ok = 'u' == c;
                    if (!ok) { break; }
                    istr.get();
                    c = istr.peek();
                    ok = 'e' == c;
                    if (!ok) { break; }
                    istr.get();
                    break;
                }
            case 'f':
                {
                    int c = istr.peek();
                    ok = 'a' == c;
                    if (!ok) { break; }
                    istr.get();
                    c = istr.peek();
                    ok = 'l' == c;
                    if (!ok) { break; }
                    istr.get();
                    c = istr.peek();
                    ok = 's' == c;
                    if (!ok) { break; }
                    istr.get();
                    c = istr.peek();
                    ok = 'e' == c;
                    if (!ok) { break; }
                    istr.get();
                    break;
                }
            case 'n':
                {
                    int c = istr.peek();
                    ok = 'u' == c;
                    if (!ok) { break; }
                    istr.get();
                    c = istr.peek();
                    ok = 'l' == c;
                    if (!ok) { break; }
                    istr.get();
                    c = istr.peek();
                    ok = 'l' == c;
                    if (!ok) { break; }
                    istr.get();
                    break;
                }
            default:
                {
                    break;
                }
            }


            if (!ok)
            {
                throw Poco::SyntaxException("Bad word");
            }


            switch (lead)
            {
            case 't':
                {
                    _var = true;
                    break;
                }
            case 'f':
                {
                    _var = false;
                    break;
                }
            default:
                {
                    _var.empty();
                    break;
                }
            }
        }
    };


    class CArrayToken : public CUserToken
    {
    public:
        bool start(char c, std::istream& /*istr*/)
        {
            return '[' == c;
        }


        void finish(std::istream& istr)
        {
            Poco::StreamTokenizer st(istr);
            st.addToken(new Poco::WhitespaceToken, true);
            st.addToken(new CSeparatorToken);
            st.addToken(new CValueToken);
            std::vector vars;
            bool closed = false;
            bool hasError = false;
            for (Poco::Token const* j = st.next(); !hasError && !closed;)
            {
                if (!j->is(Poco::Token::USER_TOKEN) )
                {
                    hasError = true;
                    continue;
                }


                CUserToken const* k = (CUserToken const*)j;
                vars.push_back(k->Var() );


                j = st.next();
                if (!j->is(Poco::Token::SEPARATOR_TOKEN) )
                {
                    hasError = true;
                    continue;
                }


                if (j->asChar() == ']')
                {
                    closed = true;
                }
                else
                {
                    j = st.next();
                }
            }


            if (!closed) { hasError = true; }
            if (hasError) { throw Poco::SyntaxException("Bad array"); }
            _var = vars;
        }
    };


    class CObjectToken : public CUserToken
    {
    public:
        bool start(char c, std::istream& /*istr*/)
        {
            return '{' == c;
        }


        void finish(std::istream& istr)
        {
            Poco::StreamTokenizer st(istr);
            st.addToken(new Poco::WhitespaceToken, true);
            st.addToken(new CStringToken);
            st.addToken(new CSeparatorToken);
            st.addToken(new CValueToken);
            Poco::Dynamic::JsObject obj;
            bool closed = false;
            bool hasError = false;
            for (Poco::Token const* j = st.next(); !hasError && !closed;)
            {
                if (!j->is(Poco::Token::USER_TOKEN) )
                {
                    hasError = true;
                    continue;
                }


                std::string key = j->asString();


                j = st.next();
                if (j->asChar() != ':')
                {
                    hasError = true;
                    continue;
                }


                j = st.next();
                if (!j->is(Poco::Token::USER_TOKEN) )
                {
                    hasError = true;
                    continue;
                }


                obj[key] = ( (CUserToken const*)j)->Var();


                j = st.next();
                if (!j->is(Poco::Token::SEPARATOR_TOKEN) )
                {
                    hasError = true;
                    continue;
                }


                if (j->asChar() == '}')
                {
                    closed = true;
                }
                else
                {
                    j = st.next();
                }
            }


            if (!closed) { hasError = true; }
            if (hasError) { throw Poco::SyntaxException("Bad object"); }
            _var = obj;
        }
    };


    class CValueToken : public CUserToken
    {
    public:
        bool start(char c, std::istream& /*istr*/)
        {
            bool ok = c > ' ';
            if (ok) { _value = c; }
            return ok;
        }


        void finish(std::istream& istr)
        {
            char c = _value[0];
            istr.putback(c);
            Poco::StreamTokenizer st(istr);
            switch (c)
            {
            case '{':
                {
                    st.addToken(new CObjectToken);
                    break;
                }
            case '[':
                {
                    st.addToken(new CArrayToken);
                    break;
                }
            case '"':
                {
                    st.addToken(new CStringToken);
                    break;
                }
            case '-':
            case '+':
            case '.':
                {
                    st.addToken(new CNumberToken);
                    break;
                }
            default:
                {
                    if (c >= '0' && c <= '9')
                    {
                        st.addToken(new CNumberToken);
                    }
                    else
                    {
                        st.addToken(new CWordToken);
                    }
                    break;
                }
            }


            Poco::Token const* token = st.next();
            if (!token->is(Poco::Token::USER_TOKEN) )
            {
                throw Poco::SyntaxException("JSON.parse");
            }


            _var = ( (CUserToken const*)token)->Var();
        }
    };


    class CSeparatorToken : public Poco::Token
    {
    public:
        Poco::Token::Class tokenClass() const { return SEPARATOR_TOKEN; }


        bool start(char c, std::istream& /*istr*/)
        {
            bool ok = ',' == c || ':' == c || ']' == c || '}' == c;
            if (ok) { _value = c; }
            return ok;
        }
    };
};


inline
std::string CJSON::ToString(const Poco::Dynamic::Var &var,
                            const Poco::Dynamic::Var &indent)
{
    std::string gap, indentP;
    int indentVal;
    if (Poco::NumberParser::tryParse(indent, indentVal) )
    {
        for (int j = 0; j < indentVal; ++j)
        {
            indentP += ' ';
        }
    }
    else if (indent.isString() )
    {
        indentP = (std::string)indent;
    }


    return ToString(var, gap, indentP);
}


inline
std::string CJSON::ToString(const Poco::Dynamic::Var &var, std::string& gap,
                            std::string& indent)
{
    std::string result;
    std::string mind = gap;
    if (var.isEmpty() )
    {
        result = "null";
    }
    else if (var.isArray() )
    {
        gap += indent;
        std::vector partial;
        for (int j = 0, hasError = 0; !hasError; ++j)
        {
            try
            {
                partial.push_back(ToString(var[j], gap, indent) );
            }
            catch (Poco::RuntimeException&)
            {
                hasError = 1;
            }
        }


        result = partial.empty() ? "[]" : (!gap.empty() ?
            ("[\n" + gap + CUtils::Join(partial, ",\n" + gap) + "\n" + mind +
            "]") : ("[" + CUtils::Join(partial, ",") + "]" ) );
        gap = mind;
    }
    else if (var.type() == typeid(Poco::Dynamic::JsObject) )
    {
        gap += indent;
        std::vector partial;
        int addr = var;
        Poco::Dynamic::JsObject *pJsObject =
            (Poco::Dynamic::JsObject*)IntToPtr(addr);
        for (Poco::Dynamic::JsObject::const_iterator j = pJsObject->begin(),
            i = pJsObject->end(); j != i; ++j)
        {
            std::string tmp = ToString(j->second, gap, indent);
            partial.push_back(Quote(j->first) +
                (!gap.empty() ? ": " : ":") + tmp);
        }
        result = partial.empty() ? "{}" : (!gap.empty() ?
            ("{\n" + gap + CUtils::Join(partial, ",\n" + gap) + "\n" + mind +
            "}") : ("{" + CUtils::Join(partial, ",") + "}" ) );
        gap = mind;
    }
    else if (var.isString() )
    {
        result = Quote(var);
    }
    else
    {
        result = (std::string)var;
    }
    return result;
}


inline
std::string CJSON::Quote(const std::string &utf8)
{
    char const* escapable = "[\\\\\\\"\\x00-\\x1f\\x7f-\\x9f\\x{00ad}"
        "\\x{0600}-\\x{0604}\\x{070f}\\x{17b4}\\x{17b5}\\x{200c}-\\x{200f}"
        "\\x{2028}-\\x{202f}\\x{2060}-\\x{206f}\\x{feff}\\x{fff0}-\\x{ffff}]";


    if (!CUtils::IsValidUTF8(utf8.c_str() ) )
    {
        throw Poco::InvalidArgumentException(utf8 + " is not UTF encoded");
    }
    std::string result = "\"";
    std::string copy = utf8;
    Poco::RegularExpression reg(escapable, Poco::RegularExpression::RE_UTF8);
    Poco::RegularExpression::Match m;
    int test = reg.match(utf8, 0, m);
    for (size_t offset = 0; test != 0;
        test = reg.match(utf8, m.offset + m.length, m) )
    {
        size_t pos = m.offset + offset;
        std::string sub;
        char& ch = copy[pos];
        switch (ch)
        {
        case '\\':
        case '\b':
        case '\t':
        case '\n':
        case '\f':
        case '\r':
        case '"':
            {
                sub += '\\';
                sub += ch;
                break;
            }
        default:
            {
                unsigned short tmp = 2 == m.length ? *( (unsigned short*)&ch) :
                    ch;
                sub = Poco::format("%04hX", tmp);
                break;
            }
        }
        copy.replace(pos, m.length, sub);
        offset += sub.length() - m.length;
    }
    result += copy;
    result += "\"";
    return result;
}


inline
Poco::Dynamic::Var CJSON::Parse(const std::string &exp)
{
    if (!CUtils::IsValidUTF8(exp.c_str() ) )
    {
        throw Poco::InvalidArgumentException(exp + " is not UTF encoded");
    }
    std::stringstream ss(exp);
    Poco::StreamTokenizer st(ss);
    st.addToken(new Poco::WhitespaceToken, true);
    st.addToken(new CValueToken);
    Poco::Token const* token = st.next();
    if (!token->is(Poco::Token::USER_TOKEN) )
    {
        throw Poco::SyntaxException("JSON.parse");
    }
    return ( (CUserToken const*)token)->Var();
}

你可能感兴趣的:(C++ JSON)