坑!你不知道的JSON解析

背景

最近在对接国内的外卖平台,代码啥的正常开发、测试、上线都没发现什么异常的。突然,最近有运维反馈说漏了单据,一开始我以为是外卖平台那边接口异常,拉单延迟导致的,就没怎么放在心上。过了半天,运维那边继续反馈此类问题,且最开始那个订单依旧没有同步,这个时候我看了接口响应都是正常的,但是JSON解析返回了NULL,导致后续流程全错了。但是测试环境验证也没办法复现这个Bug,实在是百思不得其解。一开始我拿外卖平台返回的JSON,在百度搜一个JSON在线解析网站,解析之后也是正常的。后面我无意中发现这个JSON有个地方有点猫腻,某个Unicode字符没有编码成功,显示一个黑色方框,类似这样”�“。这里我产生了一个疑问,会不会是这个玩意导致代码出现的异常?

JSON

{"name":"\ud870**"}

在线JSON解析后的结果:

 

 

 

分析一

将这块错误的JSON块单独在程序上解析下

PHP

<?php

var_dump(json_decode('{"name":"\ud870**"}', true));
// 输出:NULL

echo json_last_error();
// 输出:10

echo json_last_error_msg();
// 输出:Single unpaired UTF-16 surrogate in unicode escape

由此可见,正是这块内容导致的JSON解析错误,Single unpaired UTF-16 surrogate in unicode escape。这里基本上找到问题了,后面看看如何解决。

一开始我照着其他博主的想法,增加一个 $flag 参数进行调整,JSON_INVALID_UTF8_IGNORE | JSON_INVALID_UTF8_SUBSTITUTE,但是依旧无法正常解析

PHP

<?php

var_dump(json_decode('{"name":"\ud870**"}', true, 512, JSON_INVALID_UTF8_IGNORE | JSON_INVALID_UTF8_SUBSTITUTE));
// 输出:NULL

后面就搜一下这个Unicode字符到底是个啥东西,发现这个可能是一个emoji表情包,这里我突发奇想,我直接通过字符串的形式直接把这个Unicode解析异常的字符直接改了行不行?思路很简单,我利用JSON解析的原理,如果”\uxxxx“这类Unicode字符返回了NULL,就证明这个JSON解析是异常的,我把这个字符直接替换掉,这样就可以实现JSON正常解析了,代价就行无法真正显示JSON正常的内容。

PHP

<?php

/**
 * 替换JSON中无效的unicode数据,常见的有emoji表情包.
 */
function replaceInvalidJson(string $json): string|null
{
    return preg_replace_callback(
        '~\\\u[a-z0-9]{4}~iu',
        function ($value) {
            if (json_decode('"' . $value[0] . '"')) {
                return $value[0];
            }
            return '?';
        },
        $json
    );
}


var_dump(json_decode(replaceInvalidJson('{"name":"\ud870**"}'), true));
// 输出:array(1) { ["name"]=> string(3) "?**" }

这里发现是OK的,我把异常的Unicode直接用"?"代替了,并不影响后续的流程。

分析二

这个是网上其他大神提供的方案,原理并不难,就是手动实现解析

PHP

<?php

function customJsonDecode($json)
{
    $comment = false;
    $out = '$x=';
    for ($i = 0; $i < strlen($json); $i++) {
        if (!$comment) {
            if (($json[$i] == '{') || ($json[$i] == '[')) $out .= ' array('; else if (($json[$i] == '}') || ($json[$i] == ']')) $out .= ')'; else if ($json[$i] == ':') $out .= '=>';
            else
                $out .= $json[$i];
        } else
            $out .= $json[$i];
        if ($json[$i] == '"' && $json[($i - 1)] != "\\")
            $comment = !$comment;
    }
    eval($out . ';');
    return $x;
}


var_dump(customJsonDecode('{"name":"\ud870**"}'));
// 输出:array(1) { ["name"]=> string(8) "\ud870**" }

也是可以正常解析,而且还原封不动保留Unicode字符,缺点也是比较明显的,所有的Unicode都不转成中文,阅读起来压力不小。

结论

正确的JSON有可能由于编码问题,部分编程语言解析会报错,这个时候需要根据实际情况进行取舍。

 

posted @ 2023-01-18 21:22  凌雨尘  阅读(187)  评论(0编辑  收藏  举报