Skip to content

Commit e2ed0ec

Browse files
committed
Fix unescaped double quotes
Improve parsing invalid JSON by detecting and fixing unescaped double quote characters inside string values. Also run tests on PHP 8.4 in github actions, allow newer PEST versions, run PHP CS Fixer in parallel and other minor dev improvements.
1 parent 09b153d commit e2ed0ec

File tree

11 files changed

+77
-57
lines changed

11 files changed

+77
-57
lines changed

.github/workflows/ci.yml

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,12 @@ name: CI (tests, code style and static analysis)
33
on: pull_request
44

55
jobs:
6-
cs:
7-
name: PHP CS Fixer
8-
runs-on: ubuntu-latest
9-
10-
steps:
11-
- name: Checkout code
12-
uses: actions/checkout@v4
13-
14-
- name: Install PHP
15-
uses: shivammathur/setup-php@v2
16-
with:
17-
php-version: '8.0'
18-
19-
- name: Install dependencies
20-
run: composer install --prefer-dist --no-progress
21-
22-
- name: Run PHP CS Fixer
23-
run: composer cs
24-
256
tests:
267
name: PestPHP Tests
278
runs-on: ubuntu-latest
289
strategy:
2910
matrix:
30-
php-versions: ['8.0', '8.1', '8.2', '8.3']
11+
php-versions: ['8.0', '8.1', '8.2', '8.3', '8.4']
3112

3213
steps:
3314
- name: Checkout code
@@ -44,8 +25,8 @@ jobs:
4425
- name: Run tests
4526
run: composer test
4627

47-
stan:
48-
name: PHPStan
28+
stanAndCs:
29+
name: Static Analysis (phpstan) and Code Style (PHP CS Fixer)
4930
runs-on: ubuntu-latest
5031

5132
steps:
@@ -62,3 +43,6 @@ jobs:
6243

6344
- name: Run PHPStan
6445
run: composer stan
46+
47+
- name: Run PHP CS Fixer
48+
run: composer cs

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
vendor
22
.php-cs-fixer.cache
33
.phpunit.result.cache
4+
.phpunit.cache
45
composer.lock

.php-cs-fixer.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,21 @@
22

33
use PhpCsFixer\Config;
44
use PhpCsFixer\Finder;
5+
use PhpCsFixer\Runner\Parallel\ParallelConfigFactory;
56

67
$finder = Finder::create()
78
->exclude(['.github', 'bin', 'git-hooks'])
89
->in(__DIR__);
910
$config = new Config();
1011

11-
return $config->setFinder($finder)
12+
return (new Config())
13+
->setFinder($finder)
14+
->setParallelConfig(ParallelConfigFactory::detect())
1215
->setRules([
13-
'@PER' => true,
16+
'@PER-CS' => true,
1417
'strict_param' => true,
15-
'single_class_element_per_statement' => false,
18+
'array_syntax' => ['syntax' => 'short'],
19+
'no_unused_imports' => true,
1620
])
1721
->setRiskyAllowed(true)
1822
->setUsingCache(true);

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
## [1.1.1] - 2024-11-20
10+
### Fixed
11+
- Improve parsing invalid JSON by detecting and fixing unescaped double quote characters inside string values.
12+
913
## [1.1.0] - 2023-07-20
1014
- The `Microseconds` value object class that wraps timestamp float values to make it easier and less error-prone to work with.
1115

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright (c) 2023 Christian Olear
1+
Copyright (c) 2024 Christian Olear
22

33
Permission is hereby granted, free of charge, to any person obtaining
44
a copy of this software and associated documentation files (the

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@
2626
"php": "^8.0"
2727
},
2828
"require-dev": {
29-
"pestphp/pest": "^1.22",
29+
"pestphp/pest": "^1.22|^2.0|^3.0",
3030
"phpstan/phpstan": "^1.8",
31-
"friendsofphp/php-cs-fixer": "^3.11"
31+
"friendsofphp/php-cs-fixer": "^3.57"
3232
},
3333
"scripts": {
3434
"test": "@php vendor/bin/pest",

phpstan.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ parameters:
33
paths:
44
- src
55
- tests
6+
reportUnmatchedIgnoredErrors: false
67
ignoreErrors:
78
- "#^Call to an undefined method Pest\\\\Expectation\\|Pest\\\\Support\\\\Extendable\\:\\:\\S+\\(\\)\\.$#"

phpunit.xml

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3-
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
4-
bootstrap="vendor/autoload.php"
5-
colors="true"
6-
>
7-
<testsuites>
8-
<testsuite name="Test Suite">
9-
<directory suffix="Test.php">./tests</directory>
10-
</testsuite>
11-
</testsuites>
12-
<coverage processUncoveredFiles="true">
13-
<include>
14-
<directory suffix=".php">./app</directory>
15-
<directory suffix=".php">./src</directory>
16-
</include>
17-
</coverage>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd" bootstrap="vendor/autoload.php" colors="true" cacheDirectory=".phpunit.cache">
3+
<testsuites>
4+
<testsuite name="Test Suite">
5+
<directory suffix="Test.php">./tests</directory>
6+
</testsuite>
7+
</testsuites>
8+
<source>
9+
<include>
10+
<directory suffix=".php">./app</directory>
11+
<directory suffix=".php">./src</directory>
12+
</include>
13+
</source>
1814
</phpunit>

src/Json.php

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,17 @@ public static function stringToArray(string $string): array
2727
}
2828

2929
/**
30-
* Try to fix JSON keys without quotes
30+
* Try to fix an invalid JSON string
3131
*
32+
* 1) Try to fix JSON keys without quotes
3233
* PHPs json_decode() doesn't work with JSON objects where the keys are not wrapped in quotes.
3334
* This method tries to fix this, when json_decode() fails to parse a JSON string.
35+
*
36+
* 2) Try to fix unescaped double quotes within a string value
3437
*/
3538
protected static function fixJsonString(string $jsonString): string
3639
{
37-
return preg_replace_callback(
40+
$jsonString = preg_replace_callback(
3841
'/(?:(\w+):(\s*".*?"\s*(?:,|}))|(\w+):(\s*[^"]+?\s*(?:,|})))/i',
3942
function ($match) {
4043
if (count($match) === 3) {
@@ -59,7 +62,20 @@ function ($match) {
5962

6063
return $key . ':' . $value;
6164
},
62-
$jsonString
65+
$jsonString,
6366
) ?? $jsonString;
67+
68+
// If JSON string contains unescaped double quotes inside a string value, try to fix it.
69+
if (preg_match('/".+?": "(.+(?<!\\\\)".+)"\s*?(,|})/', $jsonString)) {
70+
$jsonString = preg_replace_callback('/".+?": "(.+(?<!\\\\)".+)"\s*?(,|})/', function ($match) {
71+
return str_replace(
72+
$match[1],
73+
str_replace('"', '\"', $match[1]),
74+
$match[0],
75+
);
76+
}, $jsonString) ?? $jsonString;
77+
}
78+
79+
return $jsonString;
6480
}
6581
}

tests/JsonTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,20 @@
6161
]);
6262
});
6363

64+
it('fixes unescaped double quotes inside a string value', function () {
65+
$jsonString = <<<JSON
66+
{
67+
"foo": "bar "lorem" ipsum baz",
68+
"bar": "lets "do" "" "even more"
69+
}
70+
JSON;
71+
72+
expect(Json::stringToArray($jsonString))->toBe([
73+
'foo' => 'bar "lorem" ipsum baz',
74+
'bar' => 'lets "do" "" "even more',
75+
]);
76+
});
77+
6478
it('throws an exception when the string is not a (valid) JSON string', function () {
6579
Json::stringToArray('{ foo: bar ]');
6680
})->throws(InvalidJsonException::class);

0 commit comments

Comments
 (0)