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

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

有没有表演爱好者想改进这种方法?


当前回答

我知道这个问题已经过时了,但是我想通知大家,json_validate()函数的RFC已经实现,并且将成为php 8.3的一部分

这种方法将是实现问题要求的最快和更有效的方法。

Github -实现代码

RFC - json_validate()

其他回答

我不知道我的解决方案的性能或优雅性,但这就是我正在使用的:

if (preg_match('/^[\[\{]\"/', $string)) {
    $aJson = json_decode($string, true);
    if (!is_null($aJson)) {
       ... do stuff here ...
    }
}

因为我所有的JSON编码字符串都以{”开头,所以用RegEx进行测试就足够了。我对RegEx一点也不流利,所以可能有更好的方法来做到这一点。另外:strpos()可能更快。

我只是想贡献我的价值。

备注:刚刚将RegEx字符串更新为/^[\[\{]\"/,也可以找到JSON数组字符串。所以它现在在字符串的开头寻找["或{"。

使用PHPBench和下面的类,得到了以下结果:

<?php

declare(strict_types=1);

/**
 * @Revs(1000)
 * @Iterations(100)
 */
class BenchmarkJson
{
    public function benchCatchValid(): bool
    {
        $validJson = '{"validJson":true}';
        try {
            json_decode($validJson, true, 512, JSON_THROW_ON_ERROR);
            return true;
        } catch(\JsonException $exception) {}
        return false;
    }

    public function benchCatchInvalid(): bool
    {
        $invalidJson = '{"invalidJson"';
        try {
            json_decode($invalidJson, true, 512, JSON_THROW_ON_ERROR);
            return true;
        } catch(\JsonException $exception) {}
        return false;
    }

    public function benchLastErrorValid(): bool
    {
        $validJson = '{"validJson":true}';
        json_decode($validJson, true);
        return (json_last_error() === JSON_ERROR_NONE);
    }

    public function benchLastErrorInvalid(): bool
    {
        $invalidJson = '{"invalidJson"';
        json_decode($invalidJson, true);
        return (json_last_error() === JSON_ERROR_NONE);
    }

    public function benchNullValid(): bool
    {
        $validJson = '{"validJson":true}';
        return (json_decode($validJson, true) !== null);
    }

    public function benchNullInvalid(): bool
    {
        $invalidJson = '{"invalidJson"';
        return (json_decode($invalidJson, true) !== null);
    }
}

6 subjects, 600 iterations, 6,000 revs, 0 rejects, 0 failures, 0 warnings
(best [mean mode] worst) = 0.714 [1.203 1.175] 1.073 (μs)
⅀T: 721.504μs μSD/r 0.089μs μRSD/r: 7.270%
suite: 1343ab9a3590de6065bc0bc6eeb344c9f6eba642, date: 2020-01-21, stime: 12:50:14
+---------------+-----------------------+-----+------+-----+------------+---------+---------+---------+---------+---------+--------+-------+
| benchmark     | subject               | set | revs | its | mem_peak   | best    | mean    | mode    | worst   | stdev   | rstdev | diff  |
+---------------+-----------------------+-----+------+-----+------------+---------+---------+---------+---------+---------+--------+-------+
| BenchmarkJson | benchCatchValid       | 0   | 1000 | 100 | 2,980,168b | 0.954μs | 1.032μs | 1.016μs | 1.428μs | 0.062μs | 6.04%  | 1.33x |
| BenchmarkJson | benchCatchInvalid     | 0   | 1000 | 100 | 2,980,184b | 2.033μs | 2.228μs | 2.166μs | 3.001μs | 0.168μs | 7.55%  | 2.88x |
| BenchmarkJson | benchLastErrorValid   | 0   | 1000 | 100 | 2,980,184b | 1.076μs | 1.195μs | 1.169μs | 1.616μs | 0.083μs | 6.97%  | 1.54x |
| BenchmarkJson | benchLastErrorInvalid | 0   | 1000 | 100 | 2,980,184b | 0.785μs | 0.861μs | 0.863μs | 1.132μs | 0.056μs | 6.54%  | 1.11x |
| BenchmarkJson | benchNullValid        | 0   | 1000 | 100 | 2,980,168b | 0.985μs | 1.124μs | 1.077μs | 1.731μs | 0.114μs | 10.15% | 1.45x |
| BenchmarkJson | benchNullInvalid      | 0   | 1000 | 100 | 2,980,184b | 0.714μs | 0.775μs | 0.759μs | 1.073μs | 0.049μs | 6.36%  | 1.00x |
+---------------+-----------------------+-----+------+-----+------------+---------+---------+---------+---------+---------+--------+-------+

结论:检查json是否有效的最快方法是返回json_decode($json, true) !== null)。

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

这真的是最快的方法吗?

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

如果您的JSON字符串包含较短的值(例如,字符串,数字或只有1-2个属性的对象),那么在这个SO问题中的所有解决方案都得到类似的性能。

这里有一个快速的概览和比较-您可以在链接的要点中找到测试用例。最后一列使用了这个答案的代码:

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

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

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


“可能解码”逻辑/代码

在尝试解码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问题中,当将一个非字符串传递给任何其他解决方案时,您将得到显著的性能下降+错误的返回值(甚至是致命的错误)。此代码是防弹和高性能的。

function isJson($string) {
    $obj = json_decode($string);
    return json_last_error() === JSON_ERROR_NONE && gettype($obj ) == "object";
}

这是有效的,对于数字不返回true

新的更新

如果JSON很长并且你不需要使用$obj,上面的解决方案就没有很好的性能

如果你只是想检查一下,最好使用下面的函数

function isJson($string) {
    if(is_numeric($string)) return false;
    json_decode($string);
    return json_last_error() === JSON_ERROR_NONE;
}

我们需要检查传递的string是否不是数字,因为在这种情况下json_decode不会引发错误。

function isJson($str) {
    $result = false;
    if (!preg_match("/^\d+$/", trim($str))) {
        json_decode($str);
        $result = (json_last_error() == JSON_ERROR_NONE);
    }

    return $result;
}