From 1887e34ccbd8dc8542edcd9dd2f2b1ca837e3b6d Mon Sep 17 00:00:00 2001 From: Bulava Eduard Date: Tue, 16 Oct 2018 11:48:04 +0300 Subject: [PATCH 1/4] Add options to JsonDecode and JsonEncode to wrap/unwrap json data Add options to JsonDecode and JsonEncode to wrap/unwrap json data Add options to JsonDecode and JsonEncode to wrap/unwrap json data Add options to JsonDecode and JsonEncode to wrap/unwrap json data Add options to JsonDecode and JsonEncode to wrap/unwrap json data Add options to JsonDecode and JsonEncode to wrap/unwrap json data --- .../Component/Serializer/Encoder/JsonDecode.php | 16 +++++++++++++++- .../Component/Serializer/Encoder/JsonEncode.php | 6 +++++- .../Serializer/Tests/Encoder/JsonDecodeTest.php | 2 ++ .../Serializer/Tests/Encoder/JsonEncodeTest.php | 1 + 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Serializer/Encoder/JsonDecode.php b/src/Symfony/Component/Serializer/Encoder/JsonDecode.php index 5b0a432f39202..f9074a3a78ea9 100644 --- a/src/Symfony/Component/Serializer/Encoder/JsonDecode.php +++ b/src/Symfony/Component/Serializer/Encoder/JsonDecode.php @@ -42,7 +42,7 @@ public function __construct(bool $associative = false, int $depth = 512) * * @param string $data The encoded JSON string to decode * @param string $format Must be set to JsonEncoder::FORMAT - * @param array $context An optional set of options for the JSON decoder; see below + * @param array $context an optional set of options for the JSON decoder; see below * * The $context array is a simple key=>value array, with the following supported keys: * @@ -58,6 +58,9 @@ public function __construct(bool $associative = false, int $depth = 512) * json_decode_options: integer * Specifies additional options as per documentation for json_decode. * + * json_root_key: string + * Specifies root key and allow to unwrap data + * * @return mixed * * @throws NotEncodableValueException @@ -78,6 +81,16 @@ public function decode($data, $format, array $context = array()) throw new NotEncodableValueException(json_last_error_msg()); } + if ($rootKey = $context['json_root_key']) { + if (\is_array($decodedData) && array_key_exists($rootKey, $decodedData)) { + $decodedData = $decodedData[$rootKey]; + } elseif (\is_object($decodedData) && property_exists($decodedData, $rootKey)) { + $decodedData = $decodedData->$rootKey; + } else { + $decodedData = null; + } + } + return $decodedData; } @@ -100,6 +113,7 @@ private function resolveContext(array $context) 'json_decode_associative' => $this->associative, 'json_decode_recursion_depth' => $this->recursionDepth, 'json_decode_options' => 0, + 'json_root_key' => null, ); return array_merge($defaultOptions, $context); diff --git a/src/Symfony/Component/Serializer/Encoder/JsonEncode.php b/src/Symfony/Component/Serializer/Encoder/JsonEncode.php index 5cc30b75026f4..b83854cfc4381 100644 --- a/src/Symfony/Component/Serializer/Encoder/JsonEncode.php +++ b/src/Symfony/Component/Serializer/Encoder/JsonEncode.php @@ -36,6 +36,10 @@ public function encode($data, $format, array $context = array()) { $context = $this->resolveContext($context); + if (($rootKey = $context['json_root_key'])) { + $data = array($rootKey => $data); + } + $encodedJson = json_encode($data, $context['json_encode_options']); if (JSON_ERROR_NONE !== json_last_error() && (false === $encodedJson || !($context['json_encode_options'] & JSON_PARTIAL_OUTPUT_ON_ERROR))) { @@ -60,6 +64,6 @@ public function supportsEncoding($format) */ private function resolveContext(array $context = array()) { - return array_merge(array('json_encode_options' => $this->options), $context); + return array_merge(array('json_encode_options' => $this->options, 'json_root_key' => null), $context); } } diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/JsonDecodeTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/JsonDecodeTest.php index f4208f1256820..a8141f3b78b58 100644 --- a/src/Symfony/Component/Serializer/Tests/Encoder/JsonDecodeTest.php +++ b/src/Symfony/Component/Serializer/Tests/Encoder/JsonDecodeTest.php @@ -52,6 +52,8 @@ public function decodeProvider() return array( array('{"foo": "bar"}', $stdClass, array()), array('{"foo": "bar"}', $assoc, array('json_decode_associative' => true)), + array('{"baz": {"foo": "bar"}}', $stdClass, array('json_root_key' => 'baz')), + array('{"baz": {"foo": "bar"}}', $assoc, array('json_root_key' => 'baz', 'json_decode_associative' => true)), ); } diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/JsonEncodeTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/JsonEncodeTest.php index f4cbf76afc16b..55932a9fb5b05 100644 --- a/src/Symfony/Component/Serializer/Tests/Encoder/JsonEncodeTest.php +++ b/src/Symfony/Component/Serializer/Tests/Encoder/JsonEncodeTest.php @@ -46,6 +46,7 @@ public function encodeProvider() return array( array(array(), '[]', array()), array(array(), '{}', array('json_encode_options' => JSON_FORCE_OBJECT)), + array(array('bar' => 'foo'), '{"baz":{"bar":"foo"}}', array('json_root_key' => 'baz')), ); } From 120179b8503c5ad2f38bff5f3b8b13535aa58891 Mon Sep 17 00:00:00 2001 From: Bulava Eduard Date: Wed, 17 Oct 2018 10:13:05 +0300 Subject: [PATCH 2/4] moved context option to public const and use the PropertyAccess to access any node --- .../Serializer/Encoder/JsonDecode.php | 17 ++++++------ .../Serializer/Encoder/JsonEncode.php | 26 ++++++++++++++++--- .../Serializer/Encoder/JsonEncoder.php | 3 ++- .../Tests/Encoder/JsonDecodeTest.php | 7 +++-- .../Tests/Encoder/JsonEncodeTest.php | 2 +- 5 files changed, 40 insertions(+), 15 deletions(-) diff --git a/src/Symfony/Component/Serializer/Encoder/JsonDecode.php b/src/Symfony/Component/Serializer/Encoder/JsonDecode.php index f9074a3a78ea9..d88bb2855a13b 100644 --- a/src/Symfony/Component/Serializer/Encoder/JsonDecode.php +++ b/src/Symfony/Component/Serializer/Encoder/JsonDecode.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Serializer\Encoder; +use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\Serializer\Exception\NotEncodableValueException; /** @@ -24,6 +25,7 @@ class JsonDecode implements DecoderInterface private $associative; private $recursionDepth; + private $propertyAccessor; /** * Constructs a new JsonDecode instance. @@ -35,6 +37,7 @@ public function __construct(bool $associative = false, int $depth = 512) { $this->associative = $associative; $this->recursionDepth = $depth; + $this->propertyAccessor = PropertyAccess::createPropertyAccessor(); } /** @@ -58,8 +61,8 @@ public function __construct(bool $associative = false, int $depth = 512) * json_decode_options: integer * Specifies additional options as per documentation for json_decode. * - * json_root_key: string - * Specifies root key and allow to unwrap data + * JSON_PROPERTY_PATH: string + * Specifies property path and allow to unwrap data * * @return mixed * @@ -81,11 +84,9 @@ public function decode($data, $format, array $context = array()) throw new NotEncodableValueException(json_last_error_msg()); } - if ($rootKey = $context['json_root_key']) { - if (\is_array($decodedData) && array_key_exists($rootKey, $decodedData)) { - $decodedData = $decodedData[$rootKey]; - } elseif (\is_object($decodedData) && property_exists($decodedData, $rootKey)) { - $decodedData = $decodedData->$rootKey; + if ($propertyPath = $context[JsonEncoder::JSON_PROPERTY_PATH]) { + if ($this->propertyAccessor->isReadable($decodedData, $propertyPath)) { + $decodedData = $this->propertyAccessor->getValue($decodedData, $propertyPath); } else { $decodedData = null; } @@ -113,7 +114,7 @@ private function resolveContext(array $context) 'json_decode_associative' => $this->associative, 'json_decode_recursion_depth' => $this->recursionDepth, 'json_decode_options' => 0, - 'json_root_key' => null, + JsonEncoder::JSON_PROPERTY_PATH => null, ); return array_merge($defaultOptions, $context); diff --git a/src/Symfony/Component/Serializer/Encoder/JsonEncode.php b/src/Symfony/Component/Serializer/Encoder/JsonEncode.php index b83854cfc4381..3c6abf1b616cb 100644 --- a/src/Symfony/Component/Serializer/Encoder/JsonEncode.php +++ b/src/Symfony/Component/Serializer/Encoder/JsonEncode.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Serializer\Encoder; +use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\Serializer\Exception\NotEncodableValueException; /** @@ -21,10 +22,12 @@ class JsonEncode implements EncoderInterface { private $options; + private $propertyAccessor; public function __construct(int $bitmask = 0) { $this->options = $bitmask; + $this->propertyAccessor = PropertyAccess::createPropertyAccessor(); } /** @@ -36,8 +39,8 @@ public function encode($data, $format, array $context = array()) { $context = $this->resolveContext($context); - if (($rootKey = $context['json_root_key'])) { - $data = array($rootKey => $data); + if ($propertyPath = $context[JsonEncoder::JSON_PROPERTY_PATH]) { + $data = $this->wrapEncodableData($propertyPath, $data); } $encodedJson = json_encode($data, $context['json_encode_options']); @@ -64,6 +67,23 @@ public function supportsEncoding($format) */ private function resolveContext(array $context = array()) { - return array_merge(array('json_encode_options' => $this->options, 'json_root_key' => null), $context); + return array_merge(array('json_encode_options' => $this->options, JsonEncoder::JSON_PROPERTY_PATH => null), $context); + } + + /** + * Wrap data before encoding. + * + * @param string $propertyPath + * @param mixed $data + * + * @return array + */ + private function wrapEncodableData($propertyPath, $data) + { + $wrappedData = array(); + + $this->propertyAccessor->setValue($wrappedData, $propertyPath, $data); + + return $wrappedData; } } diff --git a/src/Symfony/Component/Serializer/Encoder/JsonEncoder.php b/src/Symfony/Component/Serializer/Encoder/JsonEncoder.php index f4950cb3b3f47..549a364b8e3e4 100644 --- a/src/Symfony/Component/Serializer/Encoder/JsonEncoder.php +++ b/src/Symfony/Component/Serializer/Encoder/JsonEncoder.php @@ -18,7 +18,8 @@ */ class JsonEncoder implements EncoderInterface, DecoderInterface { - const FORMAT = 'json'; + public const FORMAT = 'json'; + public const JSON_PROPERTY_PATH = 'json_property_path'; protected $encodingImpl; protected $decodingImpl; diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/JsonDecodeTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/JsonDecodeTest.php index a8141f3b78b58..79fc92e5133b5 100644 --- a/src/Symfony/Component/Serializer/Tests/Encoder/JsonDecodeTest.php +++ b/src/Symfony/Component/Serializer/Tests/Encoder/JsonDecodeTest.php @@ -52,8 +52,11 @@ public function decodeProvider() return array( array('{"foo": "bar"}', $stdClass, array()), array('{"foo": "bar"}', $assoc, array('json_decode_associative' => true)), - array('{"baz": {"foo": "bar"}}', $stdClass, array('json_root_key' => 'baz')), - array('{"baz": {"foo": "bar"}}', $assoc, array('json_root_key' => 'baz', 'json_decode_associative' => true)), + array('{"baz": {"foo": "bar"}}', $stdClass, array(JsonEncoder::JSON_PROPERTY_PATH => 'baz')), + array('{"baz": {"foo": "bar"}}', null, array(JsonEncoder::JSON_PROPERTY_PATH => 'baz.inner')), + array('{"baz": {"foo": "bar"}}', $assoc, array(JsonEncoder::JSON_PROPERTY_PATH => '[baz]', 'json_decode_associative' => true)), + array('{"baz": {"foo": "bar"}}', $assoc, array(JsonEncoder::JSON_PROPERTY_PATH => '[baz]', 'json_decode_associative' => true)), + array('{"baz": {"foo": "bar", "inner": {"key": "value"}}}', array('key' => 'value'), array(JsonEncoder::JSON_PROPERTY_PATH => '[baz][inner]', 'json_decode_associative' => true)), ); } diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/JsonEncodeTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/JsonEncodeTest.php index 55932a9fb5b05..9698ea339ac0e 100644 --- a/src/Symfony/Component/Serializer/Tests/Encoder/JsonEncodeTest.php +++ b/src/Symfony/Component/Serializer/Tests/Encoder/JsonEncodeTest.php @@ -46,7 +46,7 @@ public function encodeProvider() return array( array(array(), '[]', array()), array(array(), '{}', array('json_encode_options' => JSON_FORCE_OBJECT)), - array(array('bar' => 'foo'), '{"baz":{"bar":"foo"}}', array('json_root_key' => 'baz')), + array(array('bar' => 'foo'), '{"baz":{"bar":"foo"}}', array(JsonEncoder::JSON_PROPERTY_PATH => '[baz]')), ); } From 6d75878ec37a0a54bec887d7af20e7678e2118a3 Mon Sep 17 00:00:00 2001 From: Bulava Eduard Date: Sun, 11 Nov 2018 14:42:50 +0200 Subject: [PATCH 3/4] resolve conflict --- .../Serializer/Encoder/JsonDecode.php | 3 ++- .../Serializer/Encoder/JsonEncode.php | 27 ++++++++++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/Symfony/Component/Serializer/Encoder/JsonDecode.php b/src/Symfony/Component/Serializer/Encoder/JsonDecode.php index b8210679b5d83..214417a589372 100644 --- a/src/Symfony/Component/Serializer/Encoder/JsonDecode.php +++ b/src/Symfony/Component/Serializer/Encoder/JsonDecode.php @@ -98,6 +98,7 @@ public function decode($data, $format, array $context = array()) $associative = $context[self::ASSOCIATIVE] ?? $this->defaultContext[self::ASSOCIATIVE]; $recursionDepth = $context[self::RECURSION_DEPTH] ?? $this->defaultContext[self::RECURSION_DEPTH]; $options = $context[self::OPTIONS] ?? $this->defaultContext[self::OPTIONS]; + $propertyPath = $context[JsonEncoder::JSON_PROPERTY_PATH] ?? $this->defaultContext[JsonEncoder::JSON_PROPERTY_PATH]; $decodedData = json_decode($data, $associative, $recursionDepth, $options); @@ -105,7 +106,7 @@ public function decode($data, $format, array $context = array()) throw new NotEncodableValueException(json_last_error_msg()); } - if ($propertyPath = $context[JsonEncoder::JSON_PROPERTY_PATH]) { + if ($propertyPath) { if ($this->propertyAccessor->isReadable($decodedData, $propertyPath)) { $decodedData = $this->propertyAccessor->getValue($decodedData, $propertyPath); } else { diff --git a/src/Symfony/Component/Serializer/Encoder/JsonEncode.php b/src/Symfony/Component/Serializer/Encoder/JsonEncode.php index f8a63821986cb..24022762a2a2f 100644 --- a/src/Symfony/Component/Serializer/Encoder/JsonEncode.php +++ b/src/Symfony/Component/Serializer/Encoder/JsonEncode.php @@ -26,7 +26,7 @@ class JsonEncode implements EncoderInterface private $propertyAccessor; private $defaultContext = array( self::OPTIONS => 0, - JsonEncoder::JSON_PROPERTY_PATH => null + JsonEncoder::JSON_PROPERTY_PATH => null, ); /** @@ -52,22 +52,37 @@ public function __construct($defaultContext = array()) public function encode($data, $format, array $context = array()) { $jsonEncodeOptions = $context[self::OPTIONS] ?? $this->defaultContext[self::OPTIONS]; - $encodedJson = json_encode($data, $jsonEncodeOptions); + $propertyPath = $context[JsonEncoder::JSON_PROPERTY_PATH] ?? $this->defaultContext[JsonEncoder::JSON_PROPERTY_PATH]; - if (JSON_ERROR_NONE !== json_last_error() && (false === $encodedJson || !($jsonEncodeOptions & JSON_PARTIAL_OUTPUT_ON_ERROR))) { - if ($propertyPath = $context[JsonEncoder::JSON_PROPERTY_PATH]) { + if ($propertyPath) { $data = $this->wrapEncodableData($propertyPath, $data); } - $encodedJson = json_encode($data, $context['json_encode_options']); + $encodedJson = json_encode($data, $jsonEncodeOptions); - if (JSON_ERROR_NONE !== json_last_error() && (false === $encodedJson || !($context['json_encode_options'] & JSON_PARTIAL_OUTPUT_ON_ERROR))) { + if (JSON_ERROR_NONE !== json_last_error() && (false === $encodedJson || !($jsonEncodeOptions & JSON_PARTIAL_OUTPUT_ON_ERROR))) { throw new NotEncodableValueException(json_last_error_msg()); } return $encodedJson; } + /** + * Wrap data before encoding. + * + * @param string $propertyPath + * @param mixed $data + * + * @return array + */ + private function wrapEncodableData($propertyPath, $data) + { + $wrappedData = array(); + $this->propertyAccessor->setValue($wrappedData, $propertyPath, $data); + + return $wrappedData; + } + /** * {@inheritdoc} */ From 08267704d66aefef32603de1168b7594cf630d30 Mon Sep 17 00:00:00 2001 From: Bulava Eduard Date: Mon, 4 Mar 2019 14:16:15 +0200 Subject: [PATCH 4/4] resolve conflict --- .../Bridge/Doctrine/ContainerAwareEventManager.php | 4 ++-- .../Component/Serializer/Encoder/JsonEncode.php | 5 ++--- .../Serializer/Tests/Encoder/JsonDecodeTest.php | 14 +++++++------- .../Serializer/Tests/Encoder/JsonEncodeTest.php | 10 +++++----- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php b/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php index 66b99ecf62065..035854a813f8d 100644 --- a/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php +++ b/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php @@ -41,8 +41,8 @@ public function __construct(ContainerInterface $container) * * @param string $eventName The name of the event to dispatch. The name of the event is * the name of the method that is invoked on listeners. - * @param EventArgs $eventArgs The event arguments to pass to the event handlers/listeners. - * If not supplied, the single empty EventArgs instance is used. + * @param EventArgs $eventArgs the event arguments to pass to the event handlers/listeners. + * If not supplied, the single empty EventArgs instance is used * * @return bool */ diff --git a/src/Symfony/Component/Serializer/Encoder/JsonEncode.php b/src/Symfony/Component/Serializer/Encoder/JsonEncode.php index cb7f33df7c339..f81021a75f3c6 100644 --- a/src/Symfony/Component/Serializer/Encoder/JsonEncode.php +++ b/src/Symfony/Component/Serializer/Encoder/JsonEncode.php @@ -23,9 +23,8 @@ class JsonEncode implements EncoderInterface { const OPTIONS = 'json_encode_options'; - private $defaultContext = [ private $propertyAccessor; - private $defaultContext = [] + private $defaultContext = [ self::OPTIONS => 0, JsonEncoder::JSON_PROPERTY_PATH => null, ]; @@ -78,7 +77,7 @@ public function encode($data, $format, array $context = []) */ private function wrapEncodableData($propertyPath, $data) { - $wrappedData = array(); + $wrappedData = []; $this->propertyAccessor->setValue($wrappedData, $propertyPath, $data); return $wrappedData; diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/JsonDecodeTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/JsonDecodeTest.php index 326a4b29311e0..14b4ed61a543a 100644 --- a/src/Symfony/Component/Serializer/Tests/Encoder/JsonDecodeTest.php +++ b/src/Symfony/Component/Serializer/Tests/Encoder/JsonDecodeTest.php @@ -49,15 +49,15 @@ public function decodeProvider() $assoc = ['foo' => 'bar']; - return array( + return [ ['{"foo": "bar"}', $stdClass, []], ['{"foo": "bar"}', $assoc, ['json_decode_associative' => true]], - array('{"baz": {"foo": "bar"}}', $stdClass, array(JsonEncoder::JSON_PROPERTY_PATH => 'baz')), - array('{"baz": {"foo": "bar"}}', null, array(JsonEncoder::JSON_PROPERTY_PATH => 'baz.inner')), - array('{"baz": {"foo": "bar"}}', $assoc, array(JsonEncoder::JSON_PROPERTY_PATH => '[baz]', 'json_decode_associative' => true)), - array('{"baz": {"foo": "bar"}}', $assoc, array(JsonEncoder::JSON_PROPERTY_PATH => '[baz]', 'json_decode_associative' => true)), - array('{"baz": {"foo": "bar", "inner": {"key": "value"}}}', array('key' => 'value'), array(JsonEncoder::JSON_PROPERTY_PATH => '[baz][inner]', 'json_decode_associative' => true)), - ); + ['{"baz": {"foo": "bar"}}', $stdClass, [JsonEncoder::JSON_PROPERTY_PATH => 'baz']], + ['{"baz": {"foo": "bar"}}', null, [JsonEncoder::JSON_PROPERTY_PATH => 'baz.inner']], + ['{"baz": {"foo": "bar"}}', $assoc, [JsonEncoder::JSON_PROPERTY_PATH => '[baz]', 'json_decode_associative' => true]], + ['{"baz": {"foo": "bar"}}', $assoc, [JsonEncoder::JSON_PROPERTY_PATH => '[baz]', 'json_decode_associative' => true]], + ['{"baz": {"foo": "bar", "inner": {"key": "value"}}}', ['key' => 'value'], [JsonEncoder::JSON_PROPERTY_PATH => '[baz][inner]', 'json_decode_associative' => true]], + ]; } /** diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/JsonEncodeTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/JsonEncodeTest.php index 9698ea339ac0e..3cd579ebb08f3 100644 --- a/src/Symfony/Component/Serializer/Tests/Encoder/JsonEncodeTest.php +++ b/src/Symfony/Component/Serializer/Tests/Encoder/JsonEncodeTest.php @@ -43,11 +43,11 @@ public function testEncode($toEncode, $expected, $context) public function encodeProvider() { - return array( - array(array(), '[]', array()), - array(array(), '{}', array('json_encode_options' => JSON_FORCE_OBJECT)), - array(array('bar' => 'foo'), '{"baz":{"bar":"foo"}}', array(JsonEncoder::JSON_PROPERTY_PATH => '[baz]')), - ); + return [ + [[], '[]', []], + [[], '{}', ['json_encode_options' => JSON_FORCE_OBJECT]], + [['bar' => 'foo'], '{"baz":{"bar":"foo"}}', [JsonEncoder::JSON_PROPERTY_PATH => '[baz]']], + ]; } /**