Skip to content

[Serializer] Add support for seld/jsonlint #51172

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@
"predis/predis": "^1.1|^2.0",
"psr/http-client": "^1.0",
"psr/simple-cache": "^1.0|^2.0|^3.0",
"seld/jsonlint": "^1.10",
"symfony/mercure-bundle": "^0.3",
"symfony/phpunit-bridge": "^5.4|^6.0|^7.0",
"symfony/runtime": "self.version",
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/Serializer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ CHANGELOG
* Deprecate Doctrine annotations support in favor of native attributes
* Deprecate passing an annotation reader to the constructor of `AnnotationLoader`
* Allow the `Groups` attribute/annotation on classes
* JsonDecode: Add `json_decode_detailed_errors` option

6.3
---
Expand Down
27 changes: 24 additions & 3 deletions src/Symfony/Component/Serializer/Encoder/JsonDecode.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@

namespace Symfony\Component\Serializer\Encoder;

use Seld\JsonLint\JsonParser;
use Symfony\Component\Serializer\Exception\NotEncodableValueException;
use Symfony\Component\Serializer\Exception\UnsupportedException;

/**
* Decodes JSON data.
Expand All @@ -30,6 +32,11 @@ class JsonDecode implements DecoderInterface
*/
public const ASSOCIATIVE = 'json_decode_associative';

/**
* True to enable seld/jsonlint as a source for more specific error messages when json_decode fails.
*/
public const DETAILED_ERROR_MESSAGES = 'json_decode_detailed_errors';

public const OPTIONS = 'json_decode_options';

/**
Expand All @@ -39,6 +46,7 @@ class JsonDecode implements DecoderInterface

private array $defaultContext = [
self::ASSOCIATIVE => false,
self::DETAILED_ERROR_MESSAGES => false,
self::OPTIONS => 0,
self::RECURSION_DEPTH => 512,
];
Expand Down Expand Up @@ -69,6 +77,10 @@ public function __construct(array $defaultContext = [])
* json_decode_options: integer
* Specifies additional options as per documentation for json_decode
*
* json_decode_detailed_errors: bool
* If true, enables seld/jsonlint as a source for more specific error messages when json_decode fails.
* If false or not specified, this method will use default error messages from PHP's json_decode
*
* @throws NotEncodableValueException
*
* @see https://php.net/json_decode
Expand All @@ -89,11 +101,20 @@ public function decode(string $data, string $format, array $context = []): mixed
return $decodedData;
}

if (\JSON_ERROR_NONE !== json_last_error()) {
throw new NotEncodableValueException(json_last_error_msg());
if (\JSON_ERROR_NONE === json_last_error()) {
return $decodedData;
}
$errorMessage = json_last_error_msg();

if (!($context[self::DETAILED_ERROR_MESSAGES] ?? $this->defaultContext[self::DETAILED_ERROR_MESSAGES])) {
throw new NotEncodableValueException($errorMessage);
}

if (!class_exists(JsonParser::class)) {
throw new UnsupportedException(sprintf('Enabling "%s" serializer option requires seld/jsonlint. Try running "composer require seld/jsonlint".', self::DETAILED_ERROR_MESSAGES));
}

return $decodedData;
throw new NotEncodableValueException((new JsonParser())->lint($data)?->getMessage() ?: $errorMessage);
}

public function supportsDecoding(string $format): bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,20 @@ public static function decodeProvider()
/**
* @dataProvider decodeProviderException
*/
public function testDecodeWithException($value)
public function testDecodeWithException(string $value, string $expectedExceptionMessage, array $context)
{
$this->expectException(UnexpectedValueException::class);
$this->decode->decode($value, JsonEncoder::FORMAT);
$this->expectExceptionMessage($expectedExceptionMessage);
$this->decode->decode($value, JsonEncoder::FORMAT, $context);
}

public static function decodeProviderException()
{
return [
["{'foo': 'bar'}"],
['kaboom!'],
["{'foo': 'bar'}", 'Syntax error', []],
["{'foo': 'bar'}", 'single quotes instead of double quotes', ['json_decode_detailed_errors' => true]],
['kaboom!', 'Syntax error', ['json_decode_detailed_errors' => false]],
['kaboom!', "Expected one of: 'STRING', 'NUMBER', 'NULL',", ['json_decode_detailed_errors' => true]],
];
}
}
1 change: 1 addition & 0 deletions src/Symfony/Component/Serializer/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
},
"require-dev": {
"doctrine/annotations": "^1.12|^2",
"seld/jsonlint": "^1.10",
"phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0",
"symfony/cache": "^5.4|^6.0|^7.0",
"symfony/config": "^5.4|^6.0|^7.0",
Expand Down