diff --git a/Parser/JsonSerializableParser.php b/Parser/JsonSerializableParser.php
new file mode 100644
index 0000000..2ff1b97
--- /dev/null
+++ b/Parser/JsonSerializableParser.php
@@ -0,0 +1,94 @@
+hasMethod('__construct')) {
+ foreach ($ref->getMethod('__construct')->getParameters() as $parameter) {
+ if (!$parameter->isOptional()) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function parse(array $input)
+ {
+ /** @var \JsonSerializable $obj */
+ $obj = new $input['class']();
+
+ $encoded = $obj->jsonSerialize();
+ $parsed = $this->getItemMetaData($encoded);
+
+ if (isset($input['name']) && !empty($input['name'])) {
+ $output = array();
+ $output[$input['name']] = $parsed;
+ return $output;
+ }
+
+ return $parsed['children'];
+ }
+
+ public function getItemMetaData($item)
+ {
+ $type = gettype($item);
+
+ $meta = array(
+ 'dataType' => $type == 'NULL' ? null : $type,
+ 'actualType' => $type,
+ 'subType' => null,
+ 'required' => null,
+ 'description' => null,
+ 'readonly' => null
+ );
+
+ 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/Parser/ValidationParser.php b/Parser/ValidationParser.php
index 27a671a..a0b6496 100644
--- a/Parser/ValidationParser.php
+++ b/Parser/ValidationParser.php
@@ -69,7 +69,24 @@ class ValidationParser implements ParserInterface, PostParserInterface
{
$className = $input['class'];
- return $this->doParse($className, array());
+ $parsed = $this->doParse($className, array());
+
+ if (isset($input['name']) && !empty($input['name'])) {
+ $output = array();
+ $output[$input['name']] = array(
+ 'dataType' => 'object',
+ 'actualType' => 'object',
+ 'class' => $className,
+ 'subType' => null,
+ 'required' => null,
+ 'description' => null,
+ 'readonly' => null,
+ 'children' => $parsed
+ );
+ return $output;
+ }
+
+ return $parsed;
}
/**
diff --git a/Resources/config/services.xml b/Resources/config/services.xml
index cc2fe13..3b5bfca 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,11 @@
+
+
+
+
+
diff --git a/Resources/doc/index.md b/Resources/doc/index.md
index 6185cd3..bca5c56 100644
--- a/Resources/doc/index.md
+++ b/Resources/doc/index.md
@@ -164,7 +164,7 @@ class YourController
* `parameters`: an array of parameters;
* `input`: the input type associated to the method (currently this supports Form Types, classes with JMS Serializer
- metadata, and classes with Validation component metadata) useful for POST|PUT methods, either as FQCN or as form type
+ metadata, classes with Validation component metadata and classes that implement JsonSerializable) useful for POST|PUT methods, either as FQCN or as form type
(if it is registered in the form factory in the container).
* `output`: the output type associated with the response. Specified and parsed the same way as `input`.
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..f4db0d3
--- /dev/null
+++ b/Tests/Parser/JsonSerializableParserTest.php
@@ -0,0 +1,100 @@
+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',
+ 'actualType' => 'array',
+ 'subType' => null,
+ 'required' => null,
+ 'description' => null,
+ 'readonly' => null
+ )
+ )
+ )
+ )
+ );
+ }
+
+ 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
+ )
+ );
+ }
+}