Json::Value root; root["action"] = "run";
|
首先是 []运算符重载
, 统一不同的重载类型
调用resolveReference()
进行统一的添加k
操作
首先会进行 校验参数, 然后将key封装成一个对象CZString (封装过程为将传入指针保存到 CZString对象中的cstr_)
封装完成后再保存k v
的map<CZString, Value>
中查找有无相同的CZString(key)有的话返回Value引用,
没有则创建新的<CZStrng, 空Value>存入map并返回Value的引用
然后是=运算符重载
, “run”自动转换成Value对象
转换过程中, 通过duplicateAndPrefixStringValue()
将”run”进行了封装
char* string_; // if allocated_, ptr to { unsigned, char[] }.
将长度封装到了一个char指针中, 有点类似自己设计tcp协议…
=运算符重载
函数将[]运算符重载
返回的对象引用 中的相关值swap()
成新的Value对象中的相关值
到这里理解了在Jsoncpp中 一切都是Value 包括<K, V>键值对也是在Value对象中存储
每一个<k, v>都可以作为一个Value对象, 这样既实现了复杂嵌套Json中 v为对象的情况 妙啊
union ValueHolder { LargestInt int_; LargestUInt uint_; double real_; bool bool_; char* string_;
ObjectValues* map_; } value_;
|
下面的switch的这个type 会在很多地方被修改掉.
起初 使用默认构造函数的value type是nullxxx
然后调用[]运算符重载
的时候会修改掉 type 为 objectValue
void BuiltStyledStreamWriter::writeValue(Value const& value) { switch (value.type()) { case nullValue: pushValue(nullSymbol_); break; case intValue: pushValue(valueToString(value.asLargestInt())); break; case uintValue: pushValue(valueToString(value.asLargestUInt())); break; case realValue: pushValue(valueToString(value.asDouble(), useSpecialFloats_, precision_, precisionType_)); break; case stringValue: { char const* str; char const* end; bool ok = value.getString(&str, &end); if (ok) pushValue(valueToQuotedStringN(str, static_cast<unsigned>(end - str), emitUTF8_)); else pushValue(""); break; } case booleanValue: pushValue(valueToString(value.asBool())); break; case arrayValue: writeArrayValue(value); break; case objectValue: { Value::Members members(value.getMemberNames()); if (members.empty()) pushValue("{}"); else { writeWithIndent("{"); indent(); auto it = members.begin(); for (;;) { String const& name = *it; Value const& childValue = value[name]; writeCommentBeforeValue(childValue); writeWithIndent(valueToQuotedStringN( name.data(), static_cast<unsigned>(name.length()), emitUTF8_));
*sout_ << colonSymbol_; writeValue(childValue); if (++it == members.end()) { writeCommentAfterValueOnSameLine(childValue); break; } *sout_ << ","; writeCommentAfterValueOnSameLine(childValue); } unindent(); writeWithIndent("}"); } } break; } }
|
这次是根据下面的例子分析的
int main() { Json::Value root; Json::Value data; constexpr bool shouldUseOldWay = false;
root["action"] = "run"; data["number"] = 1; root["data"] = data;
if (shouldUseOldWay) { Json::FastWriter writer; const std::string json_file = writer.write(root); std::cout << json_file << std::endl; } else { Json::StreamWriterBuilder builder; const std::string json_file = Json::writeString(builder, root); std::cout << json_file << std::endl; } return EXIT_SUCCESS; }
|
算是了解到了这个库的大概工作流程了. 能学到的部分 吃完午饭继续写.
吃完饭试了试家里的显示器, 家里的显示器还是太老了…… 八年的显示器了 看得我眼花
还是继续用笔记本吧
首先我很喜欢这种运算符重载的使用形式, 用起来非常的舒服, 需要重载两个运算符 []
和=
而且
=运算符
重载使用的swap交换需要的属性, 感觉不错
(后来我看了EffectiveC++ 发现这是=运算符处理自我赋值
太巧了)
针对需要加载配置文件的类 使用了工厂模式
writeValue使用了递归处理.
统一处理, k v都是Value对象
将用户的string 拷贝到新的char*
中 同时在开头增加了长度, 尾部补了0, 没有使用额外的变量去存储char*
的长度
不太清楚这样做有什么好处
常量全部用的 static constexpr
修饰
恰当的对象嵌套
看完了从 Value到Json 接下来看看从Json到Value
int main() { const std::string rawJson = R"({"Age": 20, "Name": "colin"})"; const auto rawJsonLength = static_cast<int>(rawJson.length()); constexpr bool shouldUseOldWay = false; JSONCPP_STRING err; Json::Value root;
if (shouldUseOldWay) { Json::Reader reader; reader.parse(rawJson, root); } else { Json::CharReaderBuilder builder;
const std::unique_ptr<Json::CharReader> reader(builder.newCharReader()); if (!reader->parse(rawJson.c_str(), rawJson.c_str() + rawJsonLength, &root, &err)) { std::cout << "error" << std::endl; return EXIT_FAILURE; } } const std::string name = root["Name"].asString(); const int age = root["Age"].asInt();
std::cout << name << std::endl; std::cout << age << std::endl; return EXIT_SUCCESS; }
|
解析的关键在于parse()
函数 传入字符串的首尾指针, 和一个Value引用
进入函数后将传入的变量保存到了自己的成员变量中.
慢慢发现 这个库将很多的默认类型 起了别名 可能是为了统一类型 类似UE4也是自己搞了一套
主要是OurReader
这个负责解析
using Char = char; using Location = const Char*;
|
解析逻辑就是parse()
调用readValue()
readValue()
负责 获取下一次数据类型type_ ->switch(type_) 根据分支决定是否递归再次调用readValue
总算把逻辑看懂了, 代码依然认为很赞
readToken()
这个函数贯穿整个解析, 通过这个函数获取到下一次的数据是什么类型, 同时移动相关的状态指针
readToken()
内部大量调用下面的函数 修改当前指针 同时返回字符, 然后switch这个字符判断下一次的数据类型
比如遇到{
就是一个对象的开始设置好type并返回 遇到}
就是对象的结束…..
OurReader::Char OurReader::getNextChar() { if (current_ == end_) return 0; return *current_++; }
|
一般第一次调用type_会被设置为对象类型, 然后readValue()
进入case 对象分支
case对象分支中
先进行了一次readToken()
获取到了k的类型, 然后根据k类型分支 将k的值转换成对应的value
之后又是一次readToken()
判断是否存在:
不存在就是错误
在之后使用这个方法, 保存k
的vallue获取v
的value再次调用readValue()
填充值 返回后继续走
Value& value = currentValue()[name]; nodes_.push(&value); bool ok = readValue();
|
读取完v
的value之后必定是,
或者}
又是一次判断 成功判断后一个k v
就获取完毕了
using Nodes = std::stack<Value*>
后面解析的代码更加的妙不可言, 使用Nodes nodes_{}
存储当前的value 实现函数之间的操作
代码合理的组织
比如case 对象分支
必定是一个k
一个:
一个v
然后一个分隔符,
或}
这些放入了一个函数
然后获取到k
之后使用Value& value = currentValue()[name]
获取v
最后依然是递归的使用
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。