Jsoncpp代码阅读

Json::Value root;
root["action"] = "run";

首先是 []运算符重载 , 统一不同的重载类型
调用resolveReference()进行统一的添加k操作

首先会进行 校验参数, 然后将key封装成一个对象CZString (封装过程为将传入指针保存到 CZString对象中的cstr_)
封装完成后再保存k vmap<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为对象的情况 妙啊

// 每个Value对象中都维护一个union
union ValueHolder {
    LargestInt int_;
    LargestUInt uint_;
    double real_;
    bool bool_;
    char* string_; // if allocated_, ptr to { unsigned, char[] }.

    // 将所有的存贮着key的CZString保存起来
    //  typedef std::map<CZString, Value> ObjectValues; // std::map<CZString, Value> 键值对
    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: {
    // Is NULL is possible for value.string_? No.
    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;

  // 左侧返回Value的引用
  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 {
    // 配置文件也是Value对象, 我用我自己.jpg
    Json::StreamWriterBuilder builder;
    const std::string json_file = Json::writeString(builder, root);
    std::cout << json_file << std::endl;
  }
  return EXIT_SUCCESS;
}

算是了解到了这个库的大概工作流程了. 能学到的部分 吃完午饭继续写.
吃完饭试了试家里的显示器, 家里的显示器还是太老了…… 八年的显示器了 看得我眼花
还是继续用笔记本吧

  1. 首先我很喜欢这种运算符重载的使用形式, 用起来非常的舒服, 需要重载两个运算符 []= 而且
    =运算符重载使用的swap交换需要的属性, 感觉不错
    (后来我看了EffectiveC++ 发现这是=运算符处理自我赋值太巧了)

  2. 针对需要加载配置文件的类 使用了工厂模式

  3. writeValue使用了递归处理.

  4. 统一处理, k v都是Value对象

  5. 将用户的string 拷贝到新的char*中 同时在开头增加了长度, 尾部补了0, 没有使用额外的变量去存储char*的长度
    不太清楚这样做有什么好处

  6. 常量全部用的 static constexpr修饰

  7. 恰当的对象嵌套


看完了从 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;

    // 根据builder的配置生成CharReader类
    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
using Char = char;
using Location = const Char*;

解析逻辑就是parse()调用readValue()
readValue()负责 获取下一次数据类型type_ ->switch(type_) 根据分支决定是否递归再次调用readValue
总算把逻辑看懂了, 代码依然认为很赞
readToken()这个函数贯穿整个解析, 通过这个函数获取到下一次的数据是什么类型, 同时移动相关的状态指针
readToken() 内部大量调用下面的函数 修改当前指针 同时返回字符, 然后switch这个字符判断下一次的数据类型
比如遇到{就是一个对象的开始设置好type并返回 遇到}就是对象的结束…..

// 获取current_指向的字符 并自增
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()填充值 返回后继续走

// 这里保存了name 这个k 将name的 v放入了顶层
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

最后依然是递归的使用