Skip to content

Instantly share code, notes, and snippets.

@stracker-phil
Last active October 4, 2022 23:57
Show Gist options
  • Save stracker-phil/6a80e6faedea8dab090b4bf6668ee461 to your computer and use it in GitHub Desktop.
Save stracker-phil/6a80e6faedea8dab090b4bf6668ee461 to your computer and use it in GitHub Desktop.
Performance comparison for various PHP functions that test, if a string is valid JSON.
<?php
// https://stackoverflow.com/a/6041773/313501#answer-6041773
function test1( $value ) {
if ( ! is_scalar( $value ) ) {
return null;
}
json_decode( $value );
return ( json_last_error() == JSON_ERROR_NONE );
}
// https://stackoverflow.com/a/12311839/313501#answer-12311839
function test2( $value ) {
if ( ! is_scalar( $value ) ) {
return null;
}
return is_object( json_decode( $value ) );
}
// https://stackoverflow.com/a/6041857/313501#answer-43244302
function test3( $value ) {
if ( ! is_scalar( $value ) ) {
return null;
}
$json = json_decode( $value );
return $json && $value != $json;
}
// https://stackoverflow.com/a/6041857/313501#answer-6041857
function test4( $value ) {
if ( ! is_scalar( $value ) ) {
return null;
}
return ! preg_match( '/[^,:{}\\[\\]0-9.\\-+Eaeflnr-u \\n\\r\\t]/',
preg_replace( '/"(\\.|[^"\\\\])*"/', '', strval( $value ) ) );
}
// https://stackoverflow.com/a/6041857/313501#answer-45241792
function test5( $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 false; }
$value = trim($value);
// 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 );
}
// ----------------------------------------------------------------------------
// Verify the correct result of the test functions.
function verify_results( $label, $testcase, $args, $echo = false ) {
$result = [];
$errors = false;
if ( $echo ) {
echo "$testcase: $label\n";
}
foreach ( $args as $test_num => $arg ) {
$fn_res = $testcase($arg['value']);
$result[$test_num] = $arg['is_valid'] === $fn_res;
if ( $echo ) {
if ( ! $result[$test_num] ) {
$errors = true;
if (is_scalar($arg['value'])) {
$value_string = $arg['value'];
} else {
$value_string = '<' . gettype( $arg['value'] ) . '>';
}
if ( strlen( $value_string ) > 40 ) {
$value_string = substr( $value_string, 0, 37 ) . '...';
}
printf(
"\tTest #%d returns %s but should be %s: %s\n",
$test_num,
$result ? 'VALID' : 'INVALID',
$arg['is_valid'] ? 'VALID' : 'INVALID',
$value_string
);
}
}
if ( is_null( $fn_res ) ) {
$result[$test_num] = null;
}
}
if ( $echo ) {
if ( ! $errors ) {
echo "\tAll results correct\n";
}
echo "\n";
}
return $result;
}
// Test-Runner runs the testcase with each test-string 10.000 times.
function run_test( $label, $testcase, $args, $num_tests = 10000 ) {
$durations = [];
echo "$testcase: $label...\n";
foreach ( $args as $test_num => $arg ) {
$start = microtime( true );
for ( $i = 0; $i < $num_tests; $i ++ ) {
$testcase( $arg['value'] );
}
$durations[$test_num] = microtime( true ) - $start;
}
return $durations;
}
// ----------------------------------------------------------------------------
// Generate the test-data and run all tests.
$large_array = [];
while ( count( $large_array ) < 1000 ) {
$large_array[] = rand( 1000, 9999 );
}
$testdata = [
[
'value' => '{"validJson":true}',
'is_valid' => true,
],
[
'value' => '{"invalidJson":true',
'is_valid' => false,
],
[
'value' => '{invalidJson:true}',
'is_valid' => false,
],
[
'value' => '{"invalidJson":true,}',
'is_valid' => false,
],
[
'value' => '"valid"',
'is_valid' => true,
],
[
'value' => '[1,2,3]',
'is_valid' => true,
],
[
'value' => '[[[1,2,3]],[4,5]]',
'is_valid' => true,
],
[
'value' => 'null',
'is_valid' => true,
],
[
'value' => 1000,
'is_valid' => true,
],
[
'value' => json_encode( $large_array ),
'is_valid' => true,
],
[
'value' => '{"id":"area","data":{"version":"2.3.1","layout_type":"inline","inline_location":"css","location_position":"after","location_selector":".weekly-schedule","close_trigger":null,"position":null,"overflow":"clip","size":"auto","close_delay":0,"close_for":"1day","min_width":"none","width":"auto","max_width":"none","min_height":"none","height":"auto","max_height":"none","z_index":"auto","role_limitation":"off","page_limitation":"on","show_close":"on","not_modal":"off","not_blocking":"off","singleton":"off","keep_closed":"off","box_shadow":"off","with_loader":"off","dark_close":"off","alt_close":"off","is_static":"off","push_content":"off","limit_role":[],"postlist":{"singular:post_type:post:id:75":"use_on"},"triggers":[{"trigger_type":"time","trigger_scroll_type":"","trigger_selector":"","trigger_delay":0,"trigger_distance":"50%","trigger_label":"After Delay Trigger","cond_custom_area":"on","cond_device_area":null,"cond_use_url_param_area":null,"cond_use_referrer_area":null,"cond_url_param_area":"","cond_referrer_area":null,"cond_custom_referrer_area":"","cond_schedule_area":null,"cond_schedule_dates_area":[],"cond_schedule_weekly_area":[]}],"triggers_hover":[],"cond_device":["desktop","tablet","phone"],"cond_use_url_param":"off","cond_use_referrer":"off","cond_url_param":"","cond_referrer":null,"cond_custom_referrer":"","cond_schedule":"weekly","cond_schedule_dates":[{"schedule_range":"2021-02-06 - 2021-02-06","schedule_start":"00:00","schedule_end":"00:00","schedule_label":""},{"schedule_range":"2021-02-06 - 2021-02-06","schedule_start":"00:00","schedule_end":"00:00","schedule_label":""},{"weekly_days":["We","Fr"],"weekly_start":"00:00","weekly_end":"23:59","weekly_label":"We, Fr"}],"cond_schedule_weekly":[]}}',
'is_valid' => true,
],
[
// The first "[" should be a "{":
'value' => '["id":"area","data":{"version":"2.3.1","layout_type":"inline","inline_location":"css","location_position":"after","location_selector":".weekly-schedule","close_trigger":null,"position":null,"overflow":"clip","size":"auto","close_delay":0,"close_for":"1day","min_width":"none","width":"auto","max_width":"none","min_height":"none","height":"auto","max_height":"none","z_index":"auto","role_limitation":"off","page_limitation":"on","show_close":"on","not_modal":"off","not_blocking":"off","singleton":"off","keep_closed":"off","box_shadow":"off","with_loader":"off","dark_close":"off","alt_close":"off","is_static":"off","push_content":"off","limit_role":[],"postlist":{"singular:post_type:post:id:75":"use_on"},"triggers":[{"trigger_type":"time","trigger_scroll_type":"","trigger_selector":"","trigger_delay":0,"trigger_distance":"50%","trigger_label":"After Delay Trigger","cond_custom_area":"on","cond_device_area":null,"cond_use_url_param_area":null,"cond_use_referrer_area":null,"cond_url_param_area":"","cond_referrer_area":null,"cond_custom_referrer_area":"","cond_schedule_area":null,"cond_schedule_dates_area":[],"cond_schedule_weekly_area":[]}],"triggers_hover":[],"cond_device":["desktop","tablet","phone"],"cond_use_url_param":"off","cond_use_referrer":"off","cond_url_param":"","cond_referrer":null,"cond_custom_referrer":"","cond_schedule":"weekly","cond_schedule_dates":[{"schedule_range":"2021-02-06 - 2021-02-06","schedule_start":"00:00","schedule_end":"00:00","schedule_label":""},{"schedule_range":"2021-02-06 - 2021-02-06","schedule_start":"00:00","schedule_end":"00:00","schedule_label":""},{"weekly_days":["We","Fr"],"weekly_start":"00:00","weekly_end":"23:59","weekly_label":"We, Fr"}],"cond_schedule_weekly":[]}}',
'is_valid' => false,
],
[
'value' => [1, 2, 3],
'is_valid' => false,
],
[
'value' => (object) ['json' => true],
'is_valid' => false,
]
];
$functions = [
'test1' => 'json_last_error() == JSON_ERROR_NONE',
'test2' => 'is_object( json_decode() )',
'test3' => 'json_decode() && $res != $string',
'test4' => 'preg_match()',
'test5' => '"maybe decode" approach',
];
echo "\nPHP version: " . PHP_VERSION . "\n\n";
// First, verify that all functions return correct values.
$valid = [];
foreach ( $functions as $fn => $label ) {
$valid[$fn] = verify_results( $label, $fn, $testdata );
}
// Measure performance.
$speed = [];
foreach ( $functions as $fn => $label ) {
$speed[$fn] = run_test( $label, $fn, $testdata );
}
// Sort results.
$performance = [];
foreach ( $testdata as $test_num => $arg ) {
$performance[$test_num] = [];
foreach ( $functions as $fn => $label ) {
if ( $valid[$fn][$test_num] ) {
$performance[$test_num][$fn] = $speed[$fn][$test_num];
} else {
unset( $speed[$fn][$test_num] );
}
}
}
$average = [];
foreach ( $functions as $fn => $label ) {
$average[$fn] = array_sum( $speed[$fn] ) / count( array_filter( $speed[$fn] ) );;
}
// Display results.
echo "\n ";
foreach ( $functions as $fn => $label ) {
echo "| $fn ";
}
echo "\n";
foreach ( $testdata as $test_num => $arg ) {
printf(
'%s #%s ',
str_repeat(' ', 3 - strlen($test_num)),
$test_num
);
foreach ( $functions as $fn => $label ) {
if ( $valid[$fn][$test_num] ) {
printf(
"| %.4f %s ",
$speed[$fn][$test_num],
$speed[$fn][$test_num] === min( $performance[$test_num] ) ? '✓︎' : ' '
);
} elseif ( is_null( $valid[$fn][$test_num] ) ) {
echo "| - ERR - ";
} else {
echo "| - INV - ";
}
}
echo "\n";
}
echo " Avg ";
foreach ( $functions as $fn => $label ) {
printf(
"| %.4f %s ",
$average[$fn],
$average[$fn] === min( $average ) ? '✓︎' : ' '
);
}
echo "\n\n";
@stracker-phil
Copy link
Author

stracker-phil commented Jan 3, 2022

Updates the test cases and improved the reporting.
The results display the average time in milliseconds for each test (lower numbers are better/faster). Also, the results now display "INV" when a comparison returns an incorrect value or throws an error.

New results:

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

Results when only comparing test1 and test5 (the two most reliable tests):

PHP version: 7.4.21

test1: json_last_error() == JSON_ERROR_NONE...
test5: "maybe decode" approach...

      | test1    | test5    
   #0 | 0.0157 ✓︎ | 0.0189   
   #1 | 0.0138   | 0.0077 ✓︎ 
   #2 | 0.0081 ✓︎ | 0.0134   
   #3 | 0.0127 ✓︎ | 0.0177   
   #4 | 0.0071 ✓︎ | 0.0134   
   #5 | 0.0116 ✓︎ | 0.0174   
   #6 | 0.0213 ✓︎ | 0.0338   
   #7 | 0.0052   | 0.0045 ✓︎ 
   #8 | 0.0063   | 0.0027 ✓︎ 
   #9 | 1.0886 ✓︎ | 1.1381   
  #10 | 0.3494 ✓︎ | 0.3611   
  #11 | 0.0074 ✓︎ | 0.0080   
  #12 | - ERR -  | 0.0024 ✓︎ 
  #13 | - ERR -  | 0.0032 ✓︎ 
  Avg | 0.1289   | 0.1173 ✓︎ 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment