注意上述方法都需要升级jsoncpp1.9.5,我是基于这个版本改的

问题:

之前用的1.7.7版本的jsoncpp,序列化double数据时会有精度损失,例如下面这个


#include <iostream>
#include "json/json.h"

int main()
{
    Json::Value obj;
    Json::FastWriter write;
    obj["d"] = 2.1;
    obj["d2"] = 9.111;
    auto rtn = write.write(obj);
    std::cout << rtn << std::endl;
    std::cout << obj.toStyledString() <<std::endl;
}

输出如下:
jsoncpp1.7.7版本double类型序列化结果

解决办法1:此法不需要改源码,使用StreamWriterBuilder进行序列化

注意:需升级jsoncpp到最新版本1.9.5版本

使用代码如下:

#include <iostream>
#include "json/json.h"
int main()
{
    Json::Value obj;
    std::ostringstream os;
    Json::StreamWriterBuilder write2;
    
    obj["d"] = 2.1;
    obj["d2"] = 9.111;
    obj["d3"] = 9.123456789;
    
    //设置精度 注意这个默认设置的是数字总长度 
    //如果想设置小数点后的位数需设置precisionType为decimal
    write2.settings_["precision"] = 7;	
    //设置精度类型 只可设置2种字符串 significant精度位数为数字总长度(jsoncpp默认为此类型) decimal精度位数为小数点后的长度
    write2.settings_["precisionType"] = "decimal";
    //注意 要先设置完settings_ 否则设置不会生效
    auto p = write2.newStreamWriter();
    p->write(obj, &os);
    std::string rtn = os.str();
    
    std::cout << rtn << std::endl;
}
  • 特别注意:精度设置一定要大于你需求的精度位数,比如需要三位可以设置4位或5位,因为最后一位可能会不准
  • 比如上面代码d3这个项是9.123456789,我设置精度为小数点后7位,然后看下面打印的输出可以发现第7位不对了,只有前6位是正确的

修改后的输出如下:
jsoncpp序列化double

不足之处:

StreamWriterBuilder序列化的字符串是可读形式的,就像上面的输出,是有换行和缩进的(转换效率会比FastWrite低),我的服务端代码里其实不需要转换json为可读的,更需要的是效率,所以还有下面一种方法改FasetWrite源码

解决办法2:此法需要改源码,使用FastWriter进行序列化

注意:需升级jsoncpp到最新版本1.9.5版本

修改源码(writer.h):

FastWriter类新增2个成员变量(precision_和precisionType_)和成员函数(set_precision和set_precisionType)


#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable : 4996) // Deriving from deprecated class
#endif
class JSON_API FastWriter
    : public Writer {
public:
  FastWriter();
  ~FastWriter() override = default;

  void enableYAMLCompatibility();

  /** \brief Drop the "null" string from the writer's output for nullValues.
   * Strictly speaking, this is not valid JSON. But when the output is being
   * fed to a browser's JavaScript, it makes for smaller output and the
   * browser can handle the output just fine.
   */
  void dropNullPlaceholders();

  void omitEndingLineFeed();

public: // overridden from Writer
  String write(const Value& root) override;

  //设置精度位数
  void set_precision(int precision) { precision_ = precision; };
  //设置精度类型 默认为数字总长
  //入参:isDecimal true表示类型为小数点后长度 false表示类型为数字总长
  void set_precisionType(bool isDecimal) { isDecimal ? (precisionType_ = PrecisionType::decimalPlaces) : (precisionType_ = PrecisionType::significantDigits); };

private:
  void writeValue(const Value& value);

  String document_;
  bool yamlCompatibilityEnabled_{false};
  bool dropNullPlaceholders_{false};
  bool omitEndingLineFeed_{false};
  int precision_{ 17 };//精度位数 默认17位
  PrecisionType precisionType_{ PrecisionType::significantDigits };//精度类型 默认为数字总长
};
#if defined(_MSC_VER)
#pragma warning(pop)
#endif

修改源码(json_writer.cpp):

只修改了1行代码,FastWriter::writeValue函数中case realValue的处理中调用的valueToString新增了2个参数传递(源码中没有传递用的函数默认值,现在传了并且可以通过新增的2个成员函数进行设置)


void FastWriter::writeValue(const Value& value) {
  switch (value.type()) {
  case nullValue:
    if (!dropNullPlaceholders_)
      document_ += "null";
    break;
  case intValue:
    document_ += valueToString(value.asLargestInt());
    break;
  case uintValue:
    document_ += valueToString(value.asLargestUInt());
    break;
  case realValue:
    //这里原先是document_ += valueToString(value.asDouble());
    //因为后2个参数没传,所以用的函数默认值即精度位数=17,精度类型=PrecisionType::significantDigits
    //修改后 现在会传这2个参数,具体值可以通过新增加的2个成员函数设置
    document_ += valueToString(value.asDouble(), precision_, precisionType_);
    break;
  case stringValue: {
    // Is NULL possible for value.string_? No.
    char const* str;
    char const* end;
    bool ok = value.getString(&str, &end);
    if (ok)
      document_ += valueToQuotedStringN(str, static_cast<size_t>(end - str));
    break;
  }
  case booleanValue:
    document_ += valueToString(value.asBool());
    break;
  case arrayValue: {
    document_ += '[';
    ArrayIndex size = value.size();
    for (ArrayIndex index = 0; index < size; ++index) {
      if (index > 0)
        document_ += ',';
      writeValue(value[index]);
    }
    document_ += ']';
  } break;
  case objectValue: {
    Value::Members members(value.getMemberNames());
    document_ += '{';
    for (auto it = members.begin(); it != members.end(); ++it) {
      const String& name = *it;
      if (it != members.begin())
        document_ += ',';
      document_ += valueToQuotedStringN(name.data(), name.length());
      document_ += yamlCompatibilityEnabled_ ? ": " : ":";
      writeValue(value[name]);
    }
    document_ += '}';
  } break;
  }
}

使用的代码:

#include <iostream>
#include "json/json.h"

int main()
{
    Json::Value obj;
    Json::FastWriter write;
    write.set_precision(7);
    write.set_precisionType(true);

    obj["d"] = 2.1;
    obj["d2"] = 9.111;
    obj["d3"] = 9.123456789;

    auto rtn = write.write(obj);
    std::cout << rtn << std::endl;
}
  • 特别注意:精度设置一定要大于你需求的精度位数,比如需要三位可以设置4位或5位,因为最后一位可能会不准
  • 比如上面代码d3这个项是9.123456789,我设置精度为小数点后7位,然后看下面打印的输出可以发现第7位不对了,只有前6位是正确的

输出如下:
jsoncpp序列化double

不足之处:

序列化后的字符串可读性比较差,但是序列化效率高,我的代码追求效率不需要可读性,所以用的这个

总结:

两种方法各有好坏,一个不需要改源码但是效率可能低一点,一个需改源码但是效率高点,大家自行选择

GitHub 加速计划 / js / json
41.72 K
6.61 K
下载
适用于现代 C++ 的 JSON。
最近提交(Master分支:20 天前 )
960b763e 3 个月前
8c391e04 6 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐