Skip to content

Commit bf5c89f

Browse files
fix(jsonschema): handle union object types in iterable properties
1 parent c0a8d88 commit bf5c89f

File tree

1 file changed

+60
-5
lines changed

1 file changed

+60
-5
lines changed

src/JsonSchema/SchemaFactory.php

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,10 @@
3131
use Symfony\Component\TypeInfo\Type\CompositeTypeInterface;
3232
use Symfony\Component\TypeInfo\Type\GenericType;
3333
use Symfony\Component\TypeInfo\Type\ObjectType;
34+
use Symfony\Component\TypeInfo\Type\UnionType;
3435
use Symfony\Component\TypeInfo\TypeIdentifier;
3536

3637
/**
37-
* {@inheritdoc}
38-
*
3938
* @author Kévin Dunglas <[email protected]>
4039
*/
4140
final class SchemaFactory implements SchemaFactoryInterface, SchemaFactoryAwareInterface
@@ -57,9 +56,6 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource
5756
$this->resourceClassResolver = $resourceClassResolver;
5857
}
5958

60-
/**
61-
* {@inheritdoc}
62-
*/
6359
public function buildSchema(string $className, string $format = 'json', string $type = Schema::TYPE_OUTPUT, ?Operation $operation = null, ?Schema $schema = null, ?array $serializerContext = null, bool $forceCollection = false): Schema
6460
{
6561
$schema = $schema ? clone $schema : new Schema();
@@ -354,6 +350,65 @@ private function buildPropertySchema(Schema $schema, string $definitionName, str
354350
$valueType = TypeHelper::getCollectionValueType($t);
355351
}
356352

353+
if ($valueType instanceof UnionType) {
354+
$unionRefs = [];
355+
356+
foreach ($valueType->getTypes() as $subtype) {
357+
if ($subtype instanceof BuiltinType && TypeIdentifier::NULL === $subtype->getTypeIdentifier()) {
358+
continue;
359+
}
360+
361+
if ($subtype instanceof ObjectType) {
362+
$className = $subtype->getClassName();
363+
} elseif ($subtype instanceof BuiltinType && $subtype->getTypeIdentifier()->isScalar()) {
364+
$unionRefs[] = match ($subtype->getTypeIdentifier()) {
365+
TypeIdentifier::INT => ['type' => 'integer'],
366+
TypeIdentifier::FLOAT => ['type' => 'number'],
367+
TypeIdentifier::BOOL => ['type' => 'boolean'],
368+
TypeIdentifier::TRUE => ['type' => 'boolean', 'const' => true],
369+
TypeIdentifier::FALSE => ['type' => 'boolean', 'const' => false],
370+
TypeIdentifier::STRING => ['type' => 'string'],
371+
default => ['type' => 'null'],
372+
};
373+
374+
continue;
375+
} else {
376+
continue;
377+
}
378+
379+
$subSchema = new Schema($version);
380+
$subSchema->setDefinitions($schema->getDefinitions());
381+
382+
$result = ($this->schemaFactory ?: $this)->buildSchema(
383+
$className,
384+
$format,
385+
$parentType,
386+
null,
387+
$subSchema,
388+
$serializerContext + [self::FORCE_SUBSCHEMA => true],
389+
);
390+
391+
if (isset($result['$ref'])) {
392+
$unionRefs[] = ['$ref' => $result['$ref']];
393+
}
394+
}
395+
396+
if ($unionRefs) {
397+
if ($isCollection) {
398+
$propertySchema['type'] = 'array';
399+
$propertySchema['items'] = 1 === \count($unionRefs)
400+
? $unionRefs[0]
401+
: ['anyOf' => $unionRefs];
402+
} else {
403+
$refs = 1 === \count($unionRefs)
404+
? [$unionRefs[0]]
405+
: $unionRefs;
406+
}
407+
}
408+
409+
continue;
410+
}
411+
357412
if (!$valueType instanceof ObjectType && !$valueType instanceof GenericType) {
358413
continue;
359414
}

0 commit comments

Comments
 (0)