C++ 解析JSON一般使用jsoncpp,使用过jsoncpp的编程人员可能都知道它是一款非常轻量的程序,而且使用起来很方便,速度也非常的快。使用的人也很多。但是美中不足的是这款原始的JSONCPP竟然没有对long的支持。刚开始使用的时候由于它基本能满足我的要求,所以我也就没有深究,但是后面我发现在解析一些比较大的数据的时候会出现一些非常奇怪的现象,会有一些精度的损失,而很多人在解析比较大的数值的时候会退而求其次,使用value.asDouble()来获取它的值。但是事实上double的范围不足以保护所有的long值的范围,所以有些时候会出现一些异常现象。
假设有如下一段json数据:
{
"longValue":444444444444444,
"name":"json",
"array":[{"cpp":"jsoncpp"},{"java":"jsonjava"},{"PHP":"support"}]
}
上面的这段json数据中有一个非常大的数据,如果将它转换为unsigned类型的会抛出异常,
如果将它转换为double类型的异常不出现了,但是出来的结果却是错误的。
上面这段错误信息是将结果转换unsigned之后出现的结果,如果将结果转换为double呢?
我们会发现结果是不正确的数值。这些都不是我们想看到的结果。
于是我萌生了对JSONCPP进行扩张的念头。下面就是我对JSONCPP扩张的一些描述,使得它可以支持long类型的数据。
1添加枚举数据类型:
enum ValueType { nullValue = 0,///< 'null' value intValue,///< signed integer value uintValue,///< unsigned integer value realValue,///< double value stringValue,///< UTF-8 string value booleanValue,///< bool value arrayValue,///< array value (ordered list) objectValue,///< object value (collection of name/value pairs). longValue,///< long value ulongValue };
2 添加long类型和unsigned long 类型的声明
typedef std::vector<std::string> Members; typedef ValueIterator iterator; typedef ValueConstIterator const_iterator; typedef Json::UInt UInt; typedef Json::Int Int; typedef Json::Long Long; typedef Json::ULong ULong; typedef UInt ArrayIndex;
3 设定maxLong,minLong,maxULong的大小说明,这些数值都是为了检测数据是否溢出用的。
static const Value null; static const Int minInt; static const Int maxInt; static const UInt maxUInt; static const Long maxLong; static const Long minLong; static const ULong maxULong;
static const Value null; static const Int minInt; static const Int maxInt; static const UInt maxUInt; static const Long maxLong; static const Long minLong; static const ULong maxULong;
4 修改decodeNumber函数,也就是当检测到该数值的大小超过了Int类型的大小之后马上转换到decodeLong数据类型中。
bool Reader::decodeNumber( Token &token ) { bool isDouble = false; for ( Location inspect = token.start_; inspect != token.end_; ++inspect ) { isDouble = isDouble || in( *inspect,'.','e','E','+' ) || ( *inspect == '-' && inspect != token.start_ ); } if ( isDouble ) return decodeDouble( token ); Location current = token.start_; bool isNegative = *current == '-'; if ( isNegative ) ++current; Value::UInt threshold = (isNegative ? Value::UInt(-Value::minInt) : Value::maxUInt) / 10; Value::UInt value = 0; while ( current < token.end_ ) { Char c = *current++; if ( c < '0' || c > '9' ) return addError( "'" + std::string( token.start_,token.end_ ) + "' is not a number.",token ); if ( value >= threshold ) return decodeLong( token ); value = value * 10 + Value::UInt(c - '0'); } if ( isNegative ) currentValue() = -Value::Int( value ); else if ( value <= Value::UInt(Value::maxInt) ) currentValue() = Value::Int( value ); else currentValue() = value; return true; }
这是修改之后的代码代码,我们可以看到当数值超过了整形的阈值之后,程序会跳转到decodeLong代码中,而decodeLong就是我添加的代码,代码如下:
bool Reader::decodeLong(Json::Reader::Token &token) { Location current = token.start_; bool isNegative = *current == '-'; if ( isNegative ) ++current; Value::ULong threshold = (isNegative ? Value::Long(-Value::minLong) : Value::maxULong) / 10; Value::ULong value = 0; while ( current < token.end_ ) { Char c = *current++; if ( c < '0' || c > '9' ) return addError( "'" + std::string( token.start_,token ); if ( value >= threshold ) return addError( "'" + std::string( token.start_,token.end_ ) + "' is too big for long.",token ); value = value * 10 + Value::UInt(c - '0'); } //9223372036854775807 if ( isNegative ) currentValue() = -Value::Long( value ); else if ( value <= Value::ULong(Value::maxLong) ) currentValue() = Value::Long( value ); else currentValue() = Value::ULong(value); return true; }
这里面的流程逻辑也不难懂,所以就不再这里做过多的说明了。
好了已经解决了对long数值的解析之后,自然是要添加Value的asLong接口和asULong接口。
asLong接口的代码如下:
Value::Long Value::asLong() const { switch ( type_ ) { case nullValue: return 0; case intValue: return value_.int_; case uintValue: return value_.uint_; case realValue: JSON_ASSERT_MESSAGE( value_.real_ >= minLong && value_.real_ <= maxLong,"Real out of signed long range" ); return Long( value_.real_ ); case booleanValue: return value_.bool_ ? 1LL : 0LL; case longValue: return value_.long_; case ulongValue: JSON_ASSERT_MESSAGE( value_.ulong_ < (unsigned)maxLong,"ulong out of signed long range" ); return Long(value_.ulong_); case stringValue: case arrayValue: case objectValue: JSON_ASSERT_MESSAGE( false,"Type is not convertible to int" ); default: JSON_ASSERT_UNREACHABLE; } return 0; // unreachable; }
在上面的代码中我们可以看到,asLong接口会根据Value的类型做一些阈值的检测然后做出相应的处理,具体的细节也不做详细的说明,可以参看上面的代码。而至于asULong的代码自然是与上面的代码大同小异,所以也不做详细的说明了。
除此之外还有很多的细节,细枝末节,我在这里都没有作出详细的说明。有兴趣的同学可以下载我修改后的代码,测试代码如下:
@H_403_74@#include <string> #include "json/json.h" void readJson(); void writeJson(); int main(int argc,char** argv) { readJson(); writeJson(); return 0; } void readJson() { using namespace std; std::string strValue = "{\"intValue\":4,\"name\":\"json\",\"array\":[{\"cpp\":\"jsoncpp\"},{\"java\":\"jsoninjava\"},{\"PHP\":\"support\"}]}"; { "longValue":44444444444444444,"name":"json","array":[{"cpp":"jsoncpp"},{"PHP":"support"}] } Json::Reader reader; Json::Value value; if (reader.parse(strValue,value)) { cout<<ULONG_LONG_MAX<<endl; unsigned long long intValue = value["intValue"].asULong(); cout<<intValue<<endl; std::string out = value["name"].asString(); std::cout << out << std::endl; const Json::Value arrayObj = value["array"]; for (unsigned int i = 0; i < arrayObj.size(); i++) { if (!arrayObj[i].isMember("cpp")) continue; out = arrayObj[i]["cpp"].asString(); std::cout << out; if (i != (arrayObj.size() - 1)) std::cout << std::endl; } } else{ cout<<reader.getFormatedErrorMessages()<<endl;; } } void writeJson() { using namespace std; Json::Value root; Json::Value arrayObj; Json::Value item; item["cpp"] = "jsoncpp"; item["java"] = "jsoninjava"; item["PHP"] = "support"; arrayObj.append(item); Json::Value item2; item2["longValue"] = LONG_LONG_MAX-10; item2["neg_longValue"] = LONG_LONG_MIN+20; item2["ulongValue"] = ULONG_LONG_MAX -10; arrayObj.append(item2); root["name"] = "json"; root["array"] = arrayObj; root.toStyledString(); std::string out = root.toStyledString(); std::cout << out << std::endl; }如果你要使用我修改之后的代码,请下载我修改之后的源代码,这些代码基本上都已经被测试过了,但是也可能还会有一些错误的出现,如果错误,敬请指教。