diff --git a/src/Symfony/Component/Serializer/Encoder/JsonDecode.php b/src/Symfony/Component/Serializer/Encoder/JsonDecode.php index 789e166b50df0..0b6480462b9c4 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; /** @@ -38,7 +39,9 @@ class JsonDecode implements DecoderInterface self::ASSOCIATIVE => false, self::OPTIONS => 0, self::RECURSION_DEPTH => 512, + JsonEncoder::JSON_PROPERTY_PATH => null, ]; + private $propertyAccessor; /** * Constructs a new JsonDecode instance. @@ -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,13 +98,21 @@ 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); if (JSON_ERROR_NONE !== json_last_error()) { 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..e8240e5c2c8cc 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; /** @@ -24,13 +25,16 @@ class JsonEncode implements EncoderInterface private $defaultContext = [ self::OPTIONS => 0, + JsonEncoder::JSON_PROPERTY_PATH => null, ]; + private $propertyAccessor; /** * @param array $defaultContext */ 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,8 +52,12 @@ public function __construct($defaultContext = []) public function encode($data, $format, array $context = []) { $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 ($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))) { throw new NotEncodableValueException(json_last_error_msg()); } @@ -64,4 +72,21 @@ public function supportsEncoding($format) { return JsonEncoder::FORMAT === $format; } + + /** + * 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 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..a3c03b64a578e 100644 --- a/src/Symfony/Component/Serializer/Tests/Encoder/JsonDecodeTest.php +++ b/src/Symfony/Component/Serializer/Tests/Encoder/JsonDecodeTest.php @@ -49,10 +49,15 @@ public function decodeProvider() $assoc = ['foo' => 'bar']; - return [ - ['{"foo": "bar"}', $stdClass, []], - ['{"foo": "bar"}', $assoc, ['json_decode_associative' => true]], - ]; + return array( + array('{"foo": "bar"}', $stdClass, array()), + array('{"foo": "bar"}', $assoc, array('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 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]']], ]; }