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/JsonDecode.php b/src/Symfony/Component/Serializer/Encoder/JsonDecode.php index 789e166b50df0..5881572c412d0 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; /** @@ -21,6 +22,7 @@ class JsonDecode implements DecoderInterface { protected $serializer; + private $propertyAccessor; /** * True to return the result as an associative array, false for a nested stdClass hierarchy. @@ -38,6 +40,7 @@ class JsonDecode implements DecoderInterface self::ASSOCIATIVE => false, self::OPTIONS => 0, self::RECURSION_DEPTH => 512, + JsonEncoder::JSON_PROPERTY_PATH => null, ]; /** @@ -57,6 +60,7 @@ public function __construct($defaultContext = [], int $depth = 512) } $this->defaultContext = array_merge($this->defaultContext, $defaultContext); + $this->propertyAccessor = PropertyAccess::createPropertyAccessor(); } /** @@ -64,7 +68,7 @@ public function __construct($defaultContext = [], 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: * @@ -80,6 +84,9 @@ public function __construct($defaultContext = [], int $depth = 512) * json_decode_options: integer * Specifies additional options as per documentation for json_decode * + * JSON_PROPERTY_PATH: string + * Specifies property path and allow to unwrap data + * * @return mixed * * @throws NotEncodableValueException @@ -91,6 +98,7 @@ public function decode($data, $format, array $context = []) $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); @@ -98,6 +106,14 @@ public function decode($data, $format, array $context = []) throw new NotEncodableValueException(json_last_error_msg()); } + if ($propertyPath) { + if ($this->propertyAccessor->isReadable($decodedData, $propertyPath)) { + $decodedData = $this->propertyAccessor->getValue($decodedData, $propertyPath); + } else { + $decodedData = null; + } + } + return $decodedData; } diff --git a/src/Symfony/Component/Serializer/Encoder/JsonEncode.php b/src/Symfony/Component/Serializer/Encoder/JsonEncode.php index ca9fc9e5d7790..f81021a75f3c6 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; /** @@ -22,8 +23,10 @@ class JsonEncode implements EncoderInterface { const OPTIONS = 'json_encode_options'; + private $propertyAccessor; private $defaultContext = [ self::OPTIONS => 0, + JsonEncoder::JSON_PROPERTY_PATH => null, ]; /** @@ -31,6 +34,7 @@ class JsonEncode implements EncoderInterface */ public function __construct($defaultContext = []) { + $this->propertyAccessor = PropertyAccess::createPropertyAccessor(); if (!\is_array($defaultContext)) { @trigger_error(sprintf('Passing an integer as first parameter of the "%s()" method is deprecated since Symfony 4.2, use the "json_encode_options" key of the context instead.', __METHOD__), E_USER_DEPRECATED); @@ -48,6 +52,12 @@ public function __construct($defaultContext = []) public function encode($data, $format, array $context = []) { $jsonEncodeOptions = $context[self::OPTIONS] ?? $this->defaultContext[self::OPTIONS]; + $propertyPath = $context[JsonEncoder::JSON_PROPERTY_PATH] ?? $this->defaultContext[JsonEncoder::JSON_PROPERTY_PATH]; + + if ($propertyPath) { + $data = $this->wrapEncodableData($propertyPath, $data); + } + $encodedJson = json_encode($data, $jsonEncodeOptions); if (JSON_ERROR_NONE !== json_last_error() && (false === $encodedJson || !($jsonEncodeOptions & JSON_PARTIAL_OUTPUT_ON_ERROR))) { @@ -57,6 +67,22 @@ public function encode($data, $format, array $context = []) return $encodedJson; } + /** + * Wrap data before encoding. + * + * @param string $propertyPath + * @param mixed $data + * + * @return array + */ + private function wrapEncodableData($propertyPath, $data) + { + $wrappedData = []; + $this->propertyAccessor->setValue($wrappedData, $propertyPath, $data); + + return $wrappedData; + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Serializer/Encoder/JsonEncoder.php b/src/Symfony/Component/Serializer/Encoder/JsonEncoder.php index 8cb98accdf775..8e0b46d88c4f4 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 c5a5551f765b2..14b4ed61a543a 100644 --- a/src/Symfony/Component/Serializer/Tests/Encoder/JsonDecodeTest.php +++ b/src/Symfony/Component/Serializer/Tests/Encoder/JsonDecodeTest.php @@ -52,6 +52,11 @@ public function decodeProvider() return [ ['{"foo": "bar"}', $stdClass, []], ['{"foo": "bar"}', $assoc, ['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 1a1a135ce0ba2..3cd579ebb08f3 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 [ [[], '[]', []], [[], '{}', ['json_encode_options' => JSON_FORCE_OBJECT]], + [['bar' => 'foo'], '{"baz":{"bar":"foo"}}', [JsonEncoder::JSON_PROPERTY_PATH => '[baz]']], ]; }