diff --git a/Parser/JsonSerializableParser.php b/Parser/JsonSerializableParser.php new file mode 100644 index 0000000..e8835ac --- /dev/null +++ b/Parser/JsonSerializableParser.php @@ -0,0 +1,86 @@ +hasMethod('__construct')) { + foreach ($ref->getMethod('__construct')->getParameters() as $parameter) { + if (!$parameter->isOptional()) { + return false; + } + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function parse(array $item) + { + /** @var \JsonSerializable $obj */ + $obj = new $item['class'](); + + $encoded = $obj->jsonSerialize(); + $top = $this->getItemMetaData($encoded); + + return $top['children']; + } + + public function getItemMetaData($item) + { + $type = gettype($item); + + $meta = array( + 'dataType' => $type, + 'required' => true, + 'description' => '', + 'readonly' => false + ); + + if ($type == 'object' && $item instanceof \JsonSerializable) { + $meta = $this->getItemMetaData($item->jsonSerialize()); + $meta['class'] = get_class($item); + } elseif (($type == 'object' && $item instanceof \stdClass) || ($type == 'array' && !$this->isSequential($item))) { + $meta['dataType'] = 'object'; + $meta['children'] = array(); + foreach ($item as $key => $value) { + $meta['children'][$key] = $this->getItemMetaData($value); + } + } + + return $meta; + } + + /** + * Check for numeric sequential keys, just like the json encoder does + * Credit: http://stackoverflow.com/a/25206156/859027 + * + * @param array $arr + * @return bool + */ + private function isSequential(array $arr) + { + for ($i = count($arr) - 1; $i >= 0; $i--) { + if (!isset($arr[$i]) && !array_key_exists($i, $arr)) { + return false; + } + } + return true; + } +} diff --git a/Resources/config/services.xml b/Resources/config/services.xml index cc2fe13..12960ce 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -16,6 +16,7 @@ Nelmio\ApiDocBundle\Parser\CollectionParser Nelmio\ApiDocBundle\Parser\FormErrorsParser + Nelmio\ApiDocBundle\Parser\JsonSerializableParser @@ -68,6 +69,10 @@ + + + + diff --git a/Tests/Fixtures/Model/JsonSerializableOptionalConstructorTest.php b/Tests/Fixtures/Model/JsonSerializableOptionalConstructorTest.php new file mode 100644 index 0000000..ad631af --- /dev/null +++ b/Tests/Fixtures/Model/JsonSerializableOptionalConstructorTest.php @@ -0,0 +1,22 @@ + 123, + 'name' => 'My name', + 'child' => array( + 'value' => array(1, 2, 3) + ), + 'another' => new JsonSerializableOptionalConstructorTest() + ); + } +} diff --git a/Tests/Parser/JsonSerializableParserTest.php b/Tests/Parser/JsonSerializableParserTest.php new file mode 100644 index 0000000..0c5c1f4 --- /dev/null +++ b/Tests/Parser/JsonSerializableParserTest.php @@ -0,0 +1,98 @@ +parser = new JsonSerializableParser(); + } + + /** + * @dataProvider dataTestParser + */ + public function testParser($property, $expected) + { + $result = $this->parser->parse(array('class' => 'Nelmio\ApiDocBundle\Tests\Fixtures\Model\JsonSerializableTest')); + foreach ($expected as $name => $value) { + $this->assertArrayHasKey($property, $result); + $this->assertArrayHasKey($name, $result[$property]); + $this->assertEquals($result[$property][$name], $expected[$name]); + } + } + + /** + * @dataProvider dataTestSupports + */ + public function testSupports($class, $expected) + { + $this->assertEquals($this->parser->supports(array('class' => $class)), $expected); + } + + public function dataTestParser() + { + return array( + array( + 'property' => 'id', + 'expected' => array( + 'dataType' => 'integer' + ) + ), + array( + 'property' => 'name', + 'expected' => array( + 'dataType' => 'string' + ) + ), + array( + 'property' => 'child', + 'expected' => array( + 'dataType' => 'object', + 'children' => array( + 'value' => array( + 'dataType' => 'array', + 'required' => true, + 'description' => '', + 'readonly' => false + ) + ) + ) + ) + ); + } + + public function dataTestSupports() + { + return array( + array( + 'class' => 'Nelmio\ApiDocBundle\Tests\Fixtures\Model\JsonSerializableTest', + 'expected' => true + ), + array( + 'class' => 'Nelmio\ApiDocBundle\Tests\Fixtures\Model\JsonSerializableRequiredConstructorTest', + 'expected' => false + ), + array( + 'class' => 'Nelmio\ApiDocBundle\Tests\Fixtures\Model\JsonSerializableOptionalConstructorTest', + 'expected' => true + ), + array( + 'class' => 'Nelmio\ApiDocBundle\Tests\Fixtures\Model\Popo', + 'expected' => false + ) + ); + } +}