RapidJSON报错: The document root must not be followed by other values.

1. 问题陈述

     首先, json报文语法格式是正确无误的, 使用json在线工具解析ok. 而且出现的问题现象是: 相同的报文格式, 有的解析成功; 而有的解析失败。比如报文:

[
  {
    "name": "lixiaogang5",
    "sex": "男",
    "address": "guizhousheng"
  }
]

备注: 这个JSON仅用于本例说明, 并非实际项目中的JSON格式字符串数据.

     概率性的某些同格式的json解析失败。解析过程:

document.Parse(pMsg);
if(document.HasParseError())
{
  // 错误处理分支
}

     因为没有增加RapidJSON解析失败的可视化字符串打印提示,所以无法查看具体报错原因。需要增加RapidJSON解析失败的错误码打印提示。
     

2. 问题排查

     粗略扫描下RapidJSON源码, 共提供了以下几个与解析失败的提示信息有关的API, 现增加到项目工程中, 重新Makefile编译并替换服务器, 重启服务并复现该问题, 观察错误日志打印.

LOG_PRT("GetParseError:[%d], GetErrorOffset:[%ld], GetParseError_En[%s], HasParseError[%d]", 
     document.GetParseError(), document.GetErrorOffset(), 
     (const RAPIDJSON_ERROR_CHARTYPE*)GetParseError_En((ParseErrorCode)document.GetParseError()), 
document.HasParseError());

     得到RapidJSON可视化字符串提示信息如下:
在这里插入图片描述

     在RapidJSON源码中, 共有以下几种可视化字符串提示信息,

//! Maps error code of parsing into error message.
/*!
    \ingroup RAPIDJSON_ERRORS
    \param parseErrorCode Error code obtained in parsing.
    \return the error message.
    \note User can make a copy of this function for localization.
        Using switch-case is safer for future modification of error codes.
*/
inline const RAPIDJSON_ERROR_CHARTYPE* GetParseError_En(ParseErrorCode parseErrorCode) {
    switch (parseErrorCode) {
        case kParseErrorNone:                           return RAPIDJSON_ERROR_STRING("No error.");

        case kParseErrorDocumentEmpty:                  return RAPIDJSON_ERROR_STRING("The document is empty.");
        case kParseErrorDocumentRootNotSingular:        return RAPIDJSON_ERROR_STRING("The document root must not be followed by other values.");
    
        case kParseErrorValueInvalid:                   return RAPIDJSON_ERROR_STRING("Invalid value.");
    
        case kParseErrorObjectMissName:                 return RAPIDJSON_ERROR_STRING("Missing a name for object member.");
        case kParseErrorObjectMissColon:                return RAPIDJSON_ERROR_STRING("Missing a colon after a name of object member.");
        case kParseErrorObjectMissCommaOrCurlyBracket:  return RAPIDJSON_ERROR_STRING("Missing a comma or '}' after an object member.");
    
        case kParseErrorArrayMissCommaOrSquareBracket:  return RAPIDJSON_ERROR_STRING("Missing a comma or ']' after an array element.");

        case kParseErrorStringUnicodeEscapeInvalidHex:  return RAPIDJSON_ERROR_STRING("Incorrect hex digit after \\u escape in string.");
        case kParseErrorStringUnicodeSurrogateInvalid:  return RAPIDJSON_ERROR_STRING("The surrogate pair in string is invalid.");
        case kParseErrorStringEscapeInvalid:            return RAPIDJSON_ERROR_STRING("Invalid escape character in string.");
        case kParseErrorStringMissQuotationMark:        return RAPIDJSON_ERROR_STRING("Missing a closing quotation mark in string.");
        case kParseErrorStringInvalidEncoding:          return RAPIDJSON_ERROR_STRING("Invalid encoding in string.");

        case kParseErrorNumberTooBig:                   return RAPIDJSON_ERROR_STRING("Number too big to be stored in double.");
        case kParseErrorNumberMissFraction:             return RAPIDJSON_ERROR_STRING("Miss fraction part in number.");
        case kParseErrorNumberMissExponent:             return RAPIDJSON_ERROR_STRING("Miss exponent in number.");

        case kParseErrorTermination:                    return RAPIDJSON_ERROR_STRING("Terminate parsing due to Handler error.");
        case kParseErrorUnspecificSyntaxError:          return RAPIDJSON_ERROR_STRING("Unspecific syntax error.");

        default:                                        return RAPIDJSON_ERROR_STRING("Unknown error.");
    }
}

     现在报错提示可视化字符串是: The document root must not be followed by other values. 翻译过来即:“文档根目录后不能跟随其他值”, 说明解析失败的JSON格式化字符串后面应该是有不可见字符数据,才导致的RapidJSON解析失败. 最为重要的是GetErrorOffset()函数提示错误的位置是559, 即:GetErrorOffset: 559 . 当前报文大小刚好是559个字节(将json串复制到Nodepad++ 中即可在底部提示当前字符串长度. ). 因此, 更加可以确定问题一定是在格式化后的JSON串末尾, 具体是啥字符, 暂时未知, 因为是一个不可终端显示的特殊字符。

     
在这里插入图片描述

     我们(模块2)待解析的JSON数据kafka broker上面消费到的, 因此, 为了确保获取到最原始的数据格式信息, 使用kafka发行包中自带的脚本工具 kafka-run-class.sh 去读取kafka log文件中的数据, 此处的时间戳(1589197149547)是解析失败的对应JSON数据, 将log文件中的数据筛选出来并追加写入1.txt文件中, 然后直接vim进去查看, 或是使用cat -v参数 1.txt 都可查看到文件中的编码格式以及具体数据。

 /home/soft/NodeServer/kafka/bin/kafka-run-class.sh kafka.tools.DumpLogSegments --files  /home/ssd/kafka/SINGLE_TRACE_INFO-2/00000000000000000000.log --print-data-log |grep "1589197149547" >>1.txt

     使用cat -v查看的效果如下所示, 可以看到在JSON格式字符串的末尾有一个特殊字符: ^A.
在这里插入图片描述
     使用vim打开查看到的效果如下:
在这里插入图片描述
      到这里时候, 问题已经定位到了, 接下来便是问题的解决方案。

备注: (1). 模块1中生产到kafka中的数据是来自其上一层的上一层(相机)模块. (2). 模块(1)使用的是cjson库, 是能够解析所有格式的报文的(包括以^A, ^M等特殊字符结尾的JSON报文). (3) 若JSON报文的内部有其他混合编码问题, 则该部分会解析失败.
     

3. 解决方案

     首先, 肯定是让数据的源头(相机取流模块)定位结尾特殊字符产生的原因, 从根本上解决掉. 其次, 增加自己模块的代码健壮性, 对RapidJSON的解析函数 doc.Parse()进行增强, 即添加一些解析的标志位组合.(如Linux下的文件句柄增加 非阻塞O_NONBLOCK 属性标志同理), doc.Parse() 函数的解析参数标志位有以下几种:

enum ParseFlag {
    kParseNoFlags = 0,              //!< No flags are set.
    kParseInsituFlag = 1,           //!< In-situ(destructive) parsing.
    kParseValidateEncodingFlag = 2, //!< Validate encoding of JSON strings.
    kParseIterativeFlag = 4,        //!< Iterative(constant complexity in terms of function call stack size) parsing.
    kParseStopWhenDoneFlag = 8,     //!< After parsing a complete JSON root from stream, stop further processing the rest of stream. When this flag is used, parser will not generate kParseErrorDocumentRootNotSingular error.
    kParseFullPrecisionFlag = 16,   //!< Parse number in full precision (but slower).
    kParseCommentsFlag = 32,        //!< Allow one-line (//) and multi-line (/**/) comments.
    kParseNumbersAsStringsFlag = 64,    //!< Parse all numbers (ints/doubles) as strings.
    kParseTrailingCommasFlag = 128, //!< Allow trailing commas at the end of objects and arrays.
    kParseNanAndInfFlag = 256,      //!< Allow parsing NaN, Inf, Infinity, -Inf and -Infinity as doubles.
    kParseDefaultFlags = RAPIDJSON_PARSE_DEFAULT_FLAGS  //!< Default parse flags. Can be customized by defining RAPIDJSON_PARSE_DEFAULT_FLAGS
};

     本次需要增加 kParseStopWhenDoneFlag(在从流解析完一个完整的JSON根之后, 停止进一步处理流的其余部分. 解析器将不会生成kParseErrorDocumentRootNotSingular错误) 标志, 因此, 可以规避RapidJSON解析Root之后的特殊字符而造成的失败.

     document.Parse<rapidjson::kParseStopWhenDoneFlag>(SerializeJsonMessage);
     if(document.HasParseError())
     {
          //错误分支逻辑处理. . .
     }

     

4. 结论

     使用修改后的代码经重编译后, 替换服务器并运行, 在该JSON问题报文源头还未解决情况下, 仍然能够解析成功不报错. 当然, JSON报文源头的模块一定得解决此问题, 因为不确定下一次的报文又是以什么特殊字符结尾.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章