在PHP中检查字符串是否为JSON的最快方法是什么?

543

我需要一种非常快的方法来检查一个字符串是否是JSON格式。我觉得下面的方法不是最好的:

function isJson($string) {
    return ((is_string($string) &&
            (is_object(json_decode($string)) ||
            is_array(json_decode($string))))) ? true : false;
}

有没有性能爱好者想要改进这个方法?


3
建议只使用一次json_decode,并检查其输入和返回值。 - user166390
8
那么,哪个是答案? - Farid Rn
12
这里的三元开关是多余的。你的语句已经被评估为布尔值。 - I wrestled a bear once.
1
接受 Lewis Donovan 的答案...它运行良好。 - Poonam Bhatt
显示剩余4条评论
38个回答

773
function isJson($string) {
   json_decode($string);
   return json_last_error() === JSON_ERROR_NONE;
}

26
大家似乎都很喜欢这个答案。有什么解释吗? - Kirk Ouimet
122
检查字符串的第一个字符是否为 {[ 或任何其他文字的第一个符号,可以在预计许多传入字符串为非 JSON 时大大提高速度。 - Oleg V. Volkov
24
$phone = '021234567'; var_dump(isJson($phone));应该返回false,而非true。 - vee
28
请注意,此函数对于任何数字都将返回true,无论您将其指定为字符串还是真实数字。例如 6.5 = true, '300' = true, 9 = true 等等。因此,这可能是一个有效的JSON值,但如果您只想检查具有{}[]的有效JSON字符串,则该函数可能不会按照您的预期行事。 - BadHorsie
21
值得注意的是,从理论上来说,这个方法是正确的。不幸的是,PHP 的 json_decode 函数存在许多 bug,这些 bug 会以奇怪的方式解析无效的 JSON。isJson('0123') 应该返回 false,因为 0123 不是 JSON,然而 isJson('123') 应该返回 true,因为 123 是 JSON。似乎有些人不知道 JSON 允许值不仅仅是对象或数组。有效的 JSON 值可以是对象、数组、数字、字符串、布尔值和 null - zzzzBov
显示剩余23条评论

234

问题的答案

json_last_error函数返回在JSON编码和解码期间发生的最后一个错误。因此,检查有效的JSON的最快方法是:

// decode the JSON data
// set second parameter boolean TRUE for associative array output.
$result = json_decode($json);

if (json_last_error() === JSON_ERROR_NONE) {
    // JSON is valid
}

// OR this is equivalent

if (json_last_error() === 0) {
    // JSON is valid
}

请注意,json_last_error 仅在 PHP >= 5.3.0 中受支持。

检查确切错误的完整程序

在开发期间了解确切的错误总是很有帮助的。这里是根据 PHP 文档检查确切错误的完整程序。
function json_validate($string)
{
    // decode the JSON data
    $result = json_decode($string);

    // switch and check possible JSON errors
    switch (json_last_error()) {
        case JSON_ERROR_NONE:
            $error = ''; // JSON is valid // No error has occurred
            break;
        case JSON_ERROR_DEPTH:
            $error = 'The maximum stack depth has been exceeded.';
            break;
        case JSON_ERROR_STATE_MISMATCH:
            $error = 'Invalid or malformed JSON.';
            break;
        case JSON_ERROR_CTRL_CHAR:
            $error = 'Control character error, possibly incorrectly encoded.';
            break;
        case JSON_ERROR_SYNTAX:
            $error = 'Syntax error, malformed JSON.';
            break;
        // PHP >= 5.3.3
        case JSON_ERROR_UTF8:
            $error = 'Malformed UTF-8 characters, possibly incorrectly encoded.';
            break;
        // PHP >= 5.5.0
        case JSON_ERROR_RECURSION:
            $error = 'One or more recursive references in the value to be encoded.';
            break;
        // PHP >= 5.5.0
        case JSON_ERROR_INF_OR_NAN:
            $error = 'One or more NAN or INF values in the value to be encoded.';
            break;
        case JSON_ERROR_UNSUPPORTED_TYPE:
            $error = 'A value of a type that cannot be encoded was given.';
            break;
        default:
            $error = 'Unknown JSON error occured.';
            break;
    }

    if ($error !== '') {
        // throw the Exception or exit // or whatever :)
        exit($error);
    }

    // everything is OK
    return $result;
}

使用有效的JSON输入进行测试

$json = '[{"user_id":13,"username":"stack"},{"user_id":14,"username":"over"}]';
$output = json_validate($json);
print_r($output);

有效输出

Array
(
    [0] => stdClass Object
        (
            [user_id] => 13
            [username] => stack
        )

    [1] => stdClass Object
        (
            [user_id] => 14
            [username] => over
        )
)

测试无效的JSON

$json = '{background-color:yellow;color:#000;padding:10px;width:650px;}';
$output = json_validate($json);
print_r($output);

Invalid OUTPUT

Syntax error, malformed JSON.

对于 (PHP >= 5.2 && PHP < 5.3.0) 版本的额外说明

由于在 PHP 5.2 中不支持 json_last_error,你可以检查编码或解码是否返回布尔值 FALSE。以下是一个示例。

// decode the JSON data
$result = json_decode($json);
if ($result === FALSE) {
    // JSON is invalid
}

小提示:如果这个 JSON 是有效的但是之前解码过的 JSON 无效,你的代码将会正确工作,因为:"返回最后一次 JSON 编码/解码期间发生的任何错误(如果有的话)。" - Bruno
谢谢@Madan,"json_decode"验证解决了我的问题,我正在运行PHP 7.0。 - Francis Rodrigues
1
json_decode肯定可以直接返回false,所以应该进行检查((strlen($json) === 5) && ($json !== 'false'))以避免这种情况。 - MrMesees
@Bruno 如果最后一次解码没有错误,则 json_last_error 返回 JSON_ERROR_NONE - Andrea

104

你真正需要做的只是这个...

if (is_object(json_decode($MyJSONArray))) 
{ 
    ... do something ...
}

甚至不需要单独的函数,只需将is_object包装在json_decode周围即可。看起来这个解决方案让人们想得太多了。


3
请明确一下,如果数组是一个简单的数组,则需要在使用is_object之前使用is_array,否则对于编码为JSON的简单数组,is_object将返回false。所以在这种情况下@ggutenberg是正确的。向json_decode传递true参数会强制返回一个对象作为数组。理论上,您可以始终将解码强制为数组,并仅检查is_array,这应该可以正常工作。 - userabuser
@userabuser 如果我对简单的PHP数组进行json_encode($array),然后执行json_decode($str),我将收到对象,而不是数组。 json_decode($str, true) 强制转换为数组。为什么在您的代码中使用复杂的字符串? 检查 is_array(json_decode($str, true)),稍后当您阅读它时,您将了解解码必须只是一个数组。更难猜测 is_object(json_decode($MyJSONArray)) “哦,这里我正在检查解码是否为数组?” - Roman M. Koss
@RomanM.Kos 不,那不正确,http://codepad.viper-7.com/OFrtsq - 就像我说的,你可以始终强制 json_decode 返回一个数组来省去检查对象和数组的步骤,但是如果你没有做到这一点,并且你开始只有一个简单的数组,当你使用 json_decode 时,你将会得到一个数组而不是一个对象。如果您要始终在传递一个简单的数组时强制使用对象进行编码,则必须使用 JSON_FORCE_OBJECT - userabuser
29
因为说了这句话而被踩:“这个请求并不需要一个单独的函数。” 严格来说,没有任何解决方案需要一个单独的函数。函数的作用并不在于使多行代码看起来像一行代码。函数的作用在于在应用程序的各个地方都使用相同的JSON检查过程,这样不同的程序员(或者同一程序员隔一段时间后)在程序流程的不同阶段不会使用不同的检查过程。 - cartbeforehorse

77
使用 json_decode 进行“探测”可能不是最快的方法。如果它是一个深层嵌套的结构,则实例化许多对象和数组只是为了将它们丢弃,这是浪费内存和时间的。因此,使用 preg_matchRFC4627 正则表达式 也可以 确保有效性,从而更快地完成任务。
  // in JS:
  var my_JSON_object = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
         text.replace(/"(\\.|[^"\\])*"/g, '')));

在PHP中相同的代码:

  return !preg_match('/[^,:{}\\[\\]0-9.\\-+Eaeflnr-u \\n\\r\\t]/',
       preg_replace('/"(\\.|[^"\\\\])*"/', '', $json_string));

我不够热衷于性能,不会在这里打扰进行基准测试。


15
在这里完成递归正则表达式以验证JSON:https://dev59.com/questions/7XE85IYBdhLWcg3w2HRa#3845829 - 但事实证明,PHP的 json_decode 总是比PCRE正则表达式更快。 (虽然它没有经过很好的优化,没有找到综合测试,并且在Perl中可能会有不同的行为..) - mario
3
好的,谢谢提醒。但是让我们把它放在这里 [错误地],这样就不会有人在实际生产中使用它了。 - mario
1
@cartbeforehorse 好的,谢谢。我已经修复了在PHP双引号字符串上下文中转义问题。 - mario
4
好的,我明白了。基本上,PHP会在正则表达式引擎看到它之前对反斜杠进行转义。就正则表达式引擎而言,字符串中的反斜杠数量是我们人类所看到数量的一半。就像正则表达式本身不够复杂一样。 - cartbeforehorse
2
我很好奇这是否比使用json_decode来查看字符串是否为有效的JSON更快,因此我进行了一些基准测试。我运行了100,000次通过使用json_encode编码的二维数组。

`PHP版本:5.5.34 平台:达尔文

test_json_decode:5.608秒。 test_regex:10.428秒。`
- Alex Plumb
显示剩余11条评论

71

如果你的字符串表示一个JSON数组或对象,这将返回true

function isJson($str) {
    $json = json_decode($str);
    return $json && $str != $json;
}

它会拒绝仅包含数字、字符串或布尔值的json字符串,尽管这些字符串在技术上是有效的json。

var_dump(isJson('{"a":5}')); // bool(true)
var_dump(isJson('[1,2,3]')); // bool(true)
var_dump(isJson('1')); // bool(false)
var_dump(isJson('1.5')); // bool(false)
var_dump(isJson('true')); // bool(false)
var_dump(isJson('false')); // bool(false)
var_dump(isJson('null')); // bool(false)
var_dump(isJson('hello')); // bool(false)
var_dump(isJson('')); // bool(false)

这是我能想到的最简洁的方式。


与其使用var_dump,你可以将其放入PHPUnit测试用例中。否则,我既惊讶又高兴地得知这是真的。 - MrMesees
4
为什么其他人的回答总是那么冗长,而这个回答却非常有效?谢谢。 - toddmo
2
简单而可爱!虽然没有检查“最快的方法”或性能方面,但这个确实涵盖了我曾经检查过的所有情况。这是一个臭名昭著的谚语“不要用大锤砸坚果”的典型例子。从程序员的角度来看,保持代码简单、短小和易于理解总是更好的选择,性能与简洁性是另一个超出本主题范围的讨论。 - Fr0zenFyr
1
@j13k 相同比较将 isJson('hello') 评估为 true,这不是有效的 json。这里故意选择了松散比较。对于空数组/对象情况,我没有快速解决方案,除了一个丑陋的 return $json == '[]' || ... - Cyril
3
@ Cyril,抱歉 - 我错过了那个情况,因为我的测试函数包括在调用json_decode之前短路函数的其他检查。因为“hello”字符串触发了JSON错误,所以函数的输出是NULL,因此额外的!is_null检查足以使用您的测试数据产生正确的结果:return $json !== false && !is_null($json) && $str != $json; - j13k
显示剩余2条评论

32

最快的方法是“可能解码”可能的JSON字符串。

这真的是最快的方法吗?

如果你想解码复杂对象或更大的数组,这是最快的解决方案!除了速度快外,这也是唯一可以可靠地处理任何类型输入值的解决方案 - 其他函数在某些情况下会抛出错误或返回不正确的结果。

如果你的JSON字符串包含短值(比如字符串、数字或只有1-2个属性的对象),那么所有在该 SO 问题中提供的解决方案都会有类似的性能

以下是一个快速概述和比较 - 你可以在链接的 gist 中找到测试用例。最后一列使用了本答案中提供的代码:

PHP version: 7.4.21

test1: json_last_error() == JSON_ERROR_NONE
test2: is_object( json_decode() )
test3: json_decode() && $res != $string
test4: preg_match()
test5: "maybe decode" approach

      | test1    | test2    | test3    | test4    | test5    
   #0 | 0.0147   | 0.0109 ✓︎ | 0.0119   | 0.0177   | 0.0194   
   #1 | 0.0129   | 0.0106   | 0.0098   | - INV -  | 0.0078 ✓︎ 
   #2 | 0.0076   | 0.0075   | 0.0063 ✓︎ | 0.0083   | 0.0133   
   #3 | 0.0126   | 0.0105   | 0.0096 ✓︎ | - INV -  | 0.0172   
   #4 | 0.0070   | - INV -  | 0.0061 ✓︎ | 0.0141   | 0.0134   
   #5 | 0.0114   | - INV -  | 0.0101   | 0.0075 ✓︎ | 0.0168   
   #6 | 0.0203   | - INV -  | 0.0195   | 0.0073 ✓︎ | 0.0259   
   #7 | 0.0046   | - INV -  | - INV -  | 0.0077   | 0.0031 ✓︎ 
   #8 | 0.0066   | - INV -  | - INV -  | 0.0081   | 0.0020 ✓︎ 
   #9 | 1.0781   | - INV -  | 1.0555   | 0.0998 ✓︎ | 1.0385   
  #10 | 0.3183 ✓︎ | 0.3246   | 0.3270   | 1.0186   | 0.3311   
  #11 | 0.0071   | 0.0068   | 0.0067 ✓︎ | - INV -  | 0.0079   
  #12 | - ERR -  | - ERR -  | - ERR -  | - ERR -  | 0.0025 ✓︎ 
  #13 | - ERR -  | - ERR -  | - ERR -  | - ERR -  | 0.0024 ✓︎ 
  Avg | 0.1251   | 0.0618 ✓︎ | 0.1463   | 0.1321   | 0.1072

请注意,最快的解决方案会产生最不准确的结果。在所有其他解决方案中,“maybe decode”方法不仅是最快的,而且还是唯一能产生正确结果的解决方案。

这是完整的性能比较脚本,您可以在其中看到我用于比较的测试数据:https://gist.github.com/stracker-phil/6a80e6faedea8dab090b4bf6668ee461


“maybe decode”逻辑/代码

我们首先执行一些类型检查和字符串比较,然后再尝试对JSON字符串进行解码。这样可以获得最佳性能,因为json_decode()可能会很慢。

/**
 * Returns true, when the given parameter is a valid JSON string.
 */
function is_json( $value ) {
    // Numeric strings are always valid JSON.
    if ( is_numeric( $value ) ) { return true; }

    // A non-string value can never be a JSON string.
    if ( ! is_string( $value ) ) { return false; }

    // Any non-numeric JSON string must be longer than 2 characters.
    if ( strlen( $value ) < 2 ) { return false; }

    // "null" is valid JSON string.
    if ( 'null' === $value ) { return true; }

    // "true" and "false" are valid JSON strings.
    if ( 'true' === $value ) { return true; }
    if ( 'false' === $value ) { return true; }

    // Any other JSON string has to be wrapped in {}, [] or "".
    if ( '{' != $value[0] && '[' != $value[0] && '"' != $value[0] ) { return false; }

    // Verify that the trailing character matches the first character.
    $last_char = $value[strlen($value) -1];
    if ( '{' == $value[0] && '}' != $last_char ) { return false; }
    if ( '[' == $value[0] && ']' != $last_char ) { return false; }
    if ( '"' == $value[0] && '"' != $last_char ) { return false; }

    // See if the string contents are valid JSON.
    return null !== json_decode( $value );
}

额外信息:使用此逻辑来安全地双重解码JSON

该函数使用相同的逻辑,但是要么返回解码后的JSON对象,要么返回原始值

我在一个解析器中使用此函数,该解析器递归解码复杂对象。一些属性可能已经被早期迭代解码了。该函数识别到这一点后不会再尝试对该值进行双重解码。

/**
 * Tests, if the given $value parameter is a JSON string.
 * When it is a valid JSON value, the decoded value is returned.
 * When the value is no JSON value (i.e. it was decoded already), then 
 * the original value is returned.
 */
function get_data( $value, $as_object = false ) {
    if ( is_numeric( $value ) ) { return 0 + $value; }
    if ( ! is_string( $value ) ) { return $value; }
    if ( strlen( $value ) < 2 ) { return $value; }
    if ( 'null' === $value ) { return null; }
    if ( 'true' === $value ) { return true; }
    if ( 'false' === $value ) { return false; }
    if ( '{' != $value[0] && '[' != $value[0] && '"' != $value[0] ) { return $value; }

    $json_data = json_decode( $value, $as_object );
    if ( is_null( $json_data ) ) { return $value; }
    return $json_data;
}

注意:在此SO问题的任何其他解决方案中传递非字符串时,您将获得极大降级的性能+错误返回值(甚至致命错误)。此代码经过强化并具有高性能。


1
我不确定为什么这个答案被踩了甚至有删除请求。我的性能测试清楚地表明,这是迄今为止最快的方法。这是性能比较脚本:https://gist.github.com/stracker-phil/6a80e6faedea8dab090b4bf6668ee461 - Philipp
1
我认为很惊奇的是,当测试拥有8个“if”语句时,它实际上比原来更快。我猜人们可能不喜欢它,因为它并不优雅,而且除非你需要检查大约一百万位文本,否则差别并不大。 - Enigma Plus
2
@EnigmaPlus 谢谢 :) 对的,这段代码不是优雅的一行代码,但问题是要找到最快的方法,而不是最优雅/最短的。json_decode更短,但需要PHP初始化一个内部JSON解析器实例,这相当复杂且比8个简单的if慢得多 - Philipp
1
你的例子 $json_data = json_decode($value,null,1); 在评估 '{"a":5}' 或 '[1,2,3]' 时返回 NULL。应该是两个级别,像这样:json_decode($value,null,2); - Eric P
1
@EricP 感谢您的反馈!我已经更新了答案,并且通过我的测试用例改进了要点,以涵盖所有内容。 - Philipp
显示剩余4条评论

29

我使用的最简单、最快的方法如下:

$json_array = json_decode( $raw_json , true );

if( $json_array == NULL )   //check if it was invalid json string
    die ('Invalid');  // Invalid JSON error

 // you can execute some else condition over here in case of valid JSON

如果输入的字符串不是JSON或者是无效的JSON,json_decode()会返回NULL。


简单的函数来验证JSON

如果您需要在多个地方验证JSON,可以使用以下函数。

function is_valid_json( $raw_json ){
    return ( json_decode( $raw_json , true ) == NULL ) ? false : true ; // Yes! thats it.
}

在上述函数中,如果它是有效的JSON,则会返回true。


4
json_decode('null') == NULL 并且 null 是一个有效的 JSON 值。 - zzzzBov
我已经在 json.parser.online 上测试了 'null' 是否为有效的 json,但似乎它不是有效的 json。而 json_decode() 是 PHP 核心函数,用于验证 json,因此我怀疑我们的输出结果可能会出现错误。 - Mohammad Mursaleen
2
不要相信一些未经验证的网站,可以参考规范进行咨询,规范与此不同(第2页)。或者,在您的开发控制台中尝试 JSON.parse('null') - zzzzBov
1
null是否是有效的JSON?的相关问题。 - ikhvjs

27

PHP 8.3

原生 PHP 函数

json_validate(string $json, int $depth = 512, int $flags = 0): bool

https://wiki.php.net/rfc/json_validate

PHP < 8.3

您必须验证输入以确保传递的字符串不为空,并且实际上是一个字符串。空字符串不是有效的 JSON。

function is_json($string) {
  return !empty($string) && is_string($string) && is_array(json_decode($string, true)) && json_last_error() == 0;
}

我认为在PHP中更重要的是确定JSON对象是否有数据,因为要使用数据,您需要调用json_encode()json_decode()。我建议拒绝空的JSON对象,这样您就不会在空数据上不必要地运行编码和解码。

function has_json_data($string) {
  $array = json_decode($string, true);
  return !empty($string) && is_string($string) && is_array($array) && !empty($array) && json_last_error() == 0;
}

+1 是指在真实世界的情境下思考问题,这是非常重要的。 - cartbeforehorse
但是'0'不是有效的JSON...我为什么要小心?@Kzqai - upful
if(is_string($string) && is_array(json_decode($string, true)) && (json_last_error() == JSON_ERROR_NONE)){ // json is valid }else{ // not valid } - Harsh Patel
请查看此博客文章 https://subinsb.com/php-check-if-string-is-json/ - Harsh Patel
3
截至2023年6月,json_validate()已正式成为PHP 8.3 Alpha-2版本的一部分。 - Asad Ali
@upful裸数字是有效的JSON,truefalsenull的值也是如此。而且,您永远不应该在常量的位置使用固定值。 - undefined

24

我认为应该这样写:json_decode($str)!=null;,否则函数应该被命名为is_not_json - Yoshi
那个函数最好改名为“不是JSON”的判断函数! - lonesomeday
3
@user166390,根据规范json_decode('null')是有效的JSON,应该返回null的值。 - zzzzBov
1
иҜ·жіЁж„ҸпјҢдҪҝз”Ёиҝҷз§Қж–№жі•is_json('false')е’Ңis_json('[]')е°Ҷиҝ”еӣһfalseпјҢеӣ дёәзұ»еһӢжңӘз»ҸжЈҖжҹҘгҖӮжҲ‘и®ӨдёәиҝҷдёӘж–№жі•еә”иҜҘиҝ”еӣһ$str === null || json_decode($str) !== nullгҖӮ - Antoine Pinsard
null是否是有效的JSON?的相关问题。 - ikhvjs

17

在昨天的工作中,我发现了一个类似的问题,并在阅读上述方法后,最终采用了一种混合方案。

function is_JSON($string) {

  return (is_null(json_decode($string))) ? FALSE : TRUE;
}

1
我以前也不习惯,呵呵。自从我使用 PhpStorm 和 Magento 代码嗅探工具后,它们总是对我抱怨,所以我开始采用这种方法。最终我们可以得到更干净的代码并且习惯了它。:P - Ricardo Martins
1
null是否是有效的JSON?的相关问题。 - ikhvjs
谢谢你提醒,@ikhvjs。如果你想预防这种情况,你可以在上面的函数内容前加上条件:if (is_null($string)) return TRUE; - Rounin
2
is_null已经返回bool,可以缩短为return is_null(json_decode($string)); - hanshenrik
可能更符合函数名称的一致性,人们期望对于一个有效的JSON格式的$string返回true...因此:return !is_null(json_decode($string)); - berend
谢谢你,@berend。有一次代码是这样的:return (is_null(json_decode($string))) ? FALSE : TRUE;。后来,我把它改成了:return is_null(json_decode($string));。我不知道当时在想什么。现在我已经改回去了。再次感谢。 - Rounin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接