前面两节介绍了jsoncpp库中使用最频繁的Value类,介绍了它的多种构造函数、重载符号、判断函数、转换函数以及其他功能性函数,同时,还举例对这些函数进行了使用说明。在jsoncpp库中,还有两个很重要的类Reader和Writer,本篇对Reader类的源码及使用进行介绍。
Json::Reader类顾名思义主要用于读取,它可以将字符串转换成Json::Value对象,或者从一个json文件中读取内容然后转成Json::Value对象。首先看它的构造函数:
//Reader类的构造函数
Reader();
Reader(const Features& features);
//Reader类构造函数的实现
Reader::Reader()
: errors_(), document_(), commentsBefore_(), features_(Features::all()) {}
Reader::Reader(const Features& features)
: errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(),
lastValue_(), commentsBefore_(), features_(features), collectComments_() {
}
//在这个实现中,初始化了一些对象,其中:
//errors_定义如下,它主要就是用于记录读取时遇到的错误,错误代码如下,大致根据英文能看出来错误类型:
Errors errors_;
enum TokenType {
tokenEndOfStream = 0,
tokenObjectBegin,
tokenObjectEnd,
tokenArrayBegin,
tokenArrayEnd,
tokenString,
tokenNumber,
tokenTrue,
tokenFalse,
tokenNull,
tokenArraySeparator,
tokenMemberSeparator,
tokenComment,
tokenError
};
class Token {
public:
TokenType type_;
Location start_;
Location end_;
};
class ErrorInfo {
public:
Token token_;
String message_;
Location extra_;
};
typedef std::deque Errors;
//存储读取的字符串
String document_;
//存储JSON的注释内容
String commentsBefore_;
//Feature类:
Features features_;
//它主要是用于设定读取时的一些特性,在Feature类的定义中,可以看到一些默认的特性,实现如下:
/// \c true if comments are allowed. Default: \c true.
bool allowComments_{true};
/// \c true if root must be either an array or an object value. Default: \c
/// false.
bool strictRoot_{false};
/// \c true if dropped null placeholders are allowed. Default: \c false.
bool allowDroppedNullPlaceholders_{false};
/// \c true if numeric object key are allowed. Default: \c false.
bool allowNumericKeys_{false};
Features::Features() = default;
Features Features::all() { return {}; }
/*Feature类的构造函数如上所示,可以看到,在Reader类的两个构造函数中,Reader()就是默认全部特性依照默认的设置,Reader(const Feature& feature)就是按用户设置的特性进行构造,这样在读取时会按照设置的特性读取。*/
在Reader类的两个构造函数中,Reader()就是默认全部特性依照默认的设置,Reader(const Feature& feature)就是按用户设置的特性进行构造,这样在读取时会按照设置的特性读取。默认情况下,一般都是使用Reader()这个构造函数的。接下来就是最重要的parse函数,它用于从一个json文件中读取内容并转换成Value对象,它的构造有如下三种形式:
bool parse(const std::string& document, Value& root, bool collectComments = true);
bool parse(const char* beginDoc, const char* endDoc, Value& root, bool collectComments = true);
bool parse(IStream& is, Value& root, bool collectComments = true);
这三个parse函数功能一致,第一个传入一个UTF-8编码格式的字符串,这个字符串就是json文件的路径,如果读取成功,则通过root返回,最后的collectCommets默认为true,是用于记录是否保留注释的。我们以默认的Reader()构造的reader对象,这里就是true,如果构造Reader时使用带有Feature版本的,将allowComments_设置为false,那么这里将不会默认保留。第二个parse接受的是两个指针,这两个指针分别指向一个UTF-8编码格式的字符串的起始位置和结束位置,同第一个一样,只是另外一种形式,它也主要是供第一个parse内部调用使用。第三个parse接受一个输入流,也就是C++中用于读取文件的流,功能与前两个相同。
parse的实现如下所示,主要功能是在parse(beginDoc, endDoc)中实现的,其它两个parse内部实现调用了这个parse。
bool parse(const char* beginDoc, const char* endDoc, Value& root, bool collectComments = true)的实现:
bool Reader::parse(const char* beginDoc,
const char* endDoc,
Value& root,
bool collectComments) {
if (!features_.allowComments_) {
collectComments = false;
}
begin_ = beginDoc;
end_ = endDoc;
collectComments_ = collectComments;
current_ = begin_;
lastValueEnd_ = nullptr;
lastValue_ = nullptr;
commentsBefore_.clear();
errors_.clear();
while (!nodes_.empty())
nodes_.pop();
nodes_.push(&root);
bool successful = readValue();
Token token;
skipCommentTokens(token);
if (collectComments_ && !commentsBefore_.empty())
root.setComment(commentsBefore_, commentAfter);
if (features_.strictRoot_) {
if (!root.isArray() && !root.isObject()) {
// Set error location to start of doc, ideally should be first token found
// in doc
token.type_ = tokenError;
token.start_ = beginDoc;
token.end_ = endDoc;
addError(
"A valid JSON document must be either an array or an object value.",
token);
return false;
}
}
return successful;
}
bool parse(const std::string& document, Value& root, bool collectComments = true)的实现:
bool Reader::parse(const std::string& document,
Value& root,
bool collectComments) {
document_.assign(document.begin(), document.end());
const char* begin = document_.c_str();
const char* end = begin + document_.length();
return parse(begin, end, root, collectComments);
}
bool parse(IStream& is, Value& root, bool collectComments = true)的实现:
bool Reader::parse(std::istream& is, Value& root, bool collectComments) {
String doc;
std::getline(is, doc, (char)EOF);
return parse(doc.data(), doc.data() + doc.size(), root, collectComments);
}
在这个源码中,首先先对一些标志位进行了初始化,将一些用于存储的指针都置空,将用于记录JSON对象的栈清空,然后直接调用了readValue进行读取,读取成功后,如果我们正常使用,不设置feature特性,则读取成功就完毕了,Value& root中就有了我们需要的JSON对象。如果设置了feature特性,则会根据所设置的特性做一些逻辑判断给出读取结果。而另外两个parse函数中,首先,string参数的那个parse实现,就是把string字符串的开始和结尾转换成char*型的begin和end再调用刚才的那个parse,实现相同。IStream参数的parse实现,是通过直接把打开的文件中的内容按行读取到一个string中,然后调用string版本的parse进行处理,实现也是相同的。所以,这里最主要的操作就是这个readValue()函数,简要看一下它的实现:
bool Reader::readValue() {
// readValue() may call itself only if it calls readObject() or ReadArray().
// These methods execute nodes_.push() just before and nodes_.pop)() just
// after calling readValue(). parse() executes one nodes_.push(), so > instead
// of >=.
if (nodes_.size() > stackLimit_g)
throwRuntimeError("Exceeded stackLimit in readValue().");
Token token;
skipCommentTokens(token);
bool successful = true;
if (collectComments_ && !commentsBefore_.empty()) {
currentValue().setComment(commentsBefore_, commentBefore);
commentsBefore_.clear();
}
switch (token.type_) {
case tokenObjectBegin:
successful = readObject(token);
currentValue().setOffsetLimit(current_ - begin_);
break;
case tokenArrayBegin:
successful = readArray(token);
currentValue().setOffsetLimit(current_ - begin_);
break;
case tokenNumber:
successful = decodeNumber(token);
break;
case tokenString:
successful = decodeString(token);
break;
case tokenTrue: {
Value v(true);
currentValue().swapPayload(v);
currentValue().setOffsetStart(token.start_ - begin_);
currentValue().setOffsetLimit(token.end_ - begin_);
} break;
case tokenFalse: {
Value v(false);
currentValue().swapPayload(v);
currentValue().setOffsetStart(token.start_ - begin_);
currentValue().setOffsetLimit(token.end_ - begin_);
} break;
case tokenNull: {
Value v;
currentValue().swapPayload(v);
currentValue().setOffsetStart(token.start_ - begin_);
currentValue().setOffsetLimit(token.end_ - begin_);
} break;
case tokenArraySeparator:
case tokenObjectEnd:
case tokenArrayEnd:
if (features_.allowDroppedNullPlaceholders_) {
// "Un-read" the current token and mark the current value as a null
// token.
current_--;
Value v;
currentValue().swapPayload(v);
currentValue().setOffsetStart(current_ - begin_ - 1);
currentValue().setOffsetLimit(current_ - begin_);
break;
} // Else, fall through...
default:
currentValue().setOffsetStart(token.start_ - begin_);
currentValue().setOffsetLimit(token.end_ - begin_);
return addError("Syntax error: value, object or array expected.", token);
}
if (collectComments_) {
lastValueEnd_ = current_;
lastValue_ = ¤tValue();
}
return successful;
}
readValue中根据当前文本的类型,如object型(带有{})、array型(带有[])、Number型(数字)、String型(字符等)、Bool型(true/false)、Null型(空)甚至是其他类型(解析不了的syntax error),走相应不同分支的实现函数,每个分支的实现函数中,实现了各自类型的读取方法,这些分支函数具体有如下这些:
bool readToken(Token& token);
void skipSpaces();
bool match(Location pattern, int patternLength);
bool readComment();
bool readCStyleComment();
bool readCppStyleComment();
bool readString();
void readNumber();
bool readValue();
bool readObject(Token& token);
bool readArray(Token& token);
bool decodeNumber(Token& token);
bool decodeNumber(Token& token, Value& decoded);
bool decodeString(Token& token);
bool decodeString(Token& token, String& decoded);
bool decodeDouble(Token& token);
bool decodeDouble(Token& token, Value& decoded);
bool decodeUnicodeCodePoint(Token& token,
Location& current,
Location end,
unsigned int& unicode);
bool decodeUnicodeEscapeSequence(Token& token,
Location& current,
Location end,
unsigned int& unicode);
bool addError(const String& message, Token& token, Location extra = nullptr);
bool recoverFromError(TokenType skipUntilToken);
bool addErrorAndRecover(const String& message,
Token& token,
TokenType skipUntilToken);
void skipUntilSpace();
Value& currentValue();
Char getNextChar();
void
getLocationLineAndColumn(Location location, int& line, int& column) const;
String getLocationLineAndColumn(Location location) const;
void addComment(Location begin, Location end, CommentPlacement placement);
void skipCommentTokens(Token& token);
static bool containsNewLine(Location begin, Location end);
static String normalizeEOL(Location begin, Location end);
这其中的判断条件比较冗杂,且属于对用户而言不重要的内容,从名字中都基本都能够看出功能,readXXX的函数多数是用来读取,decodeXXX的函数是用来从read出来的东西中解析所需的类型的,其余如addXXX是用来记录一些过程中的错误或注释等,还有一些是用于获取当前读取的指针位置、判断条件等等,具体的实现就不展开了。
那么,在Reader类的使用上,举个示例如下所示:
//字符串读取
int readFromString()
{
Json::Value root;
Json::Reader reader;
const char* json_document = "{\"age\" : 26,\"name\" : \"Hearz\", \"blog\" : \"https://mp.csdn.net/postedit?not_checkout=1\", \"isVisit\" : true}";
if(!reader.parse(json_document, root))
{
std::cout << "parse string error!" << std::endl;
return -1;
}
std::cout << "root : " << root.toStyledString() << std::endl;
return 0;
}
//从json文件读取
int readFromFile()
{
Json::Value root;
Json::Reader reader;
std::string fileName = "./jsonFile.json";
std::ifstream is(fileName, std::ios::binary);
if(!reader.parse(is, root))
{
std::cout << "parse string error!" << std::endl;
return -1;
}
std::cout << "root : " << root.toStyledString() << std::endl;
is.close();
return 0;
}
Reader类一般正常使用主要就是以上这些,其他还有一些使用不太常见,就不一一举例了。