Skip to content

Commit ec091a9

Browse files
committed
[JsonStreamer] Fix reading/writing objects with generics
1 parent 7341a19 commit ec091a9

6 files changed

+41
-9
lines changed

Mapping/GenericTypePropertyMetadataLoader.php

+4-7
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,7 @@ public function load(string $className, array $options = [], array $context = []
4343

4444
foreach ($result as &$metadata) {
4545
$type = $metadata->getType();
46-
47-
if (isset($variableTypes[(string) $type])) {
48-
$metadata = $metadata->withType($this->replaceVariableTypes($type, $variableTypes));
49-
}
46+
$metadata = $metadata->withType($this->replaceVariableTypes($type, $variableTypes));
5047
}
5148

5249
return $result;
@@ -122,19 +119,19 @@ private function replaceVariableTypes(Type $type, array $variableTypes): Type
122119
}
123120

124121
if ($type instanceof UnionType) {
125-
return new UnionType(...array_map(fn (Type $t): Type => $this->replaceVariableTypes($t, $variableTypes), $type->getTypes()));
122+
return Type::union(...array_map(fn (Type $t): Type => $this->replaceVariableTypes($t, $variableTypes), $type->getTypes()));
126123
}
127124

128125
if ($type instanceof IntersectionType) {
129-
return new IntersectionType(...array_map(fn (Type $t): Type => $this->replaceVariableTypes($t, $variableTypes), $type->getTypes()));
126+
return Type::intersection(...array_map(fn (Type $t): Type => $this->replaceVariableTypes($t, $variableTypes), $type->getTypes()));
130127
}
131128

132129
if ($type instanceof CollectionType) {
133130
return new CollectionType($this->replaceVariableTypes($type->getWrappedType(), $variableTypes), $type->isList());
134131
}
135132

136133
if ($type instanceof GenericType) {
137-
return new GenericType(
134+
return Type::generic(
138135
$this->replaceVariableTypes($type->getWrappedType(), $variableTypes),
139136
...array_map(fn (Type $t): Type => $this->replaceVariableTypes($t, $variableTypes), $type->getVariableTypes()),
140137
);

Read/StreamReaderGenerator.php

+5
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
use Symfony\Component\TypeInfo\Type\BuiltinType;
3535
use Symfony\Component\TypeInfo\Type\CollectionType;
3636
use Symfony\Component\TypeInfo\Type\EnumType;
37+
use Symfony\Component\TypeInfo\Type\GenericType;
3738
use Symfony\Component\TypeInfo\Type\ObjectType;
3839
use Symfony\Component\TypeInfo\Type\UnionType;
3940

@@ -118,6 +119,10 @@ public function createDataModel(Type $type, array $options = [], array $context
118119
return new BackedEnumNode($type);
119120
}
120121

122+
if ($type instanceof GenericType) {
123+
$type = $type->getWrappedType();
124+
}
125+
121126
if ($type instanceof ObjectType && !$type instanceof EnumType) {
122127
$typeString = (string) $type;
123128
$className = $type->getClassName();

Tests/Fixtures/Model/DummyWithGenerics.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
class DummyWithGenerics
99
{
1010
/**
11-
* @var array<int, T>
11+
* @var list<T>
1212
*/
1313
public array $dummies = [];
1414
}

Tests/JsonStreamReaderTest.php

+12
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum;
1717
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\ClassicDummy;
1818
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithDateTimes;
19+
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithGenerics;
1920
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNameAttributes;
2021
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNullableProperties;
2122
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithPhpDoc;
@@ -100,6 +101,17 @@ public function testReadObject()
100101
}, '{"id": 10, "name": "dummy name"}', Type::object(ClassicDummy::class));
101102
}
102103

104+
public function testReadObjectWithGenerics()
105+
{
106+
$reader = JsonStreamReader::create(streamReadersDir: $this->streamReadersDir, lazyGhostsDir: $this->lazyGhostsDir);
107+
108+
$this->assertRead($reader, function (mixed $read) {
109+
$this->assertInstanceOf(DummyWithGenerics::class, $read);
110+
$this->assertSame(10, $read->dummies[0]->id);
111+
$this->assertSame('dummy name', $read->dummies[0]->name);
112+
}, '{"dummies":[{"id":10,"name":"dummy name"}]}', Type::generic(Type::object(DummyWithGenerics::class), Type::object(ClassicDummy::class)));
113+
}
114+
103115
public function testReadObjectWithStreamedName()
104116
{
105117
$reader = JsonStreamReader::create(streamReadersDir: $this->streamReadersDir, lazyGhostsDir: $this->lazyGhostsDir);

Tests/JsonStreamWriterTest.php

+13
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum;
1818
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\ClassicDummy;
1919
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithDateTimes;
20+
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithGenerics;
2021
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNameAttributes;
2122
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNullableProperties;
2223
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithPhpDoc;
@@ -117,6 +118,18 @@ public function testWriteObject()
117118
$this->assertWritten('{"id":10,"name":"dummy name"}', $dummy, Type::object(ClassicDummy::class));
118119
}
119120

121+
public function testWriteObjectWithGenerics()
122+
{
123+
$nestedDummy = new DummyWithNameAttributes();
124+
$nestedDummy->id = 10;
125+
$nestedDummy->name = 'dummy name';
126+
127+
$dummy = new DummyWithGenerics();
128+
$dummy->dummies = [$nestedDummy];
129+
130+
$this->assertWritten('{"dummies":[{"id":10,"name":"dummy name"}]}', $dummy, Type::generic(Type::object(DummyWithGenerics::class), Type::object(ClassicDummy::class)));
131+
}
132+
120133
public function testWriteObjectWithStreamedName()
121134
{
122135
$dummy = new DummyWithNameAttributes();

Write/StreamWriterGenerator.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
use Symfony\Component\TypeInfo\Type\BuiltinType;
3636
use Symfony\Component\TypeInfo\Type\CollectionType;
3737
use Symfony\Component\TypeInfo\Type\EnumType;
38+
use Symfony\Component\TypeInfo\Type\GenericType;
3839
use Symfony\Component\TypeInfo\Type\ObjectType;
3940
use Symfony\Component\TypeInfo\Type\UnionType;
4041

@@ -124,6 +125,10 @@ private function createDataModel(Type $type, DataAccessorInterface $accessor, ar
124125
return new BackedEnumNode($accessor, $type);
125126
}
126127

128+
if ($type instanceof GenericType) {
129+
$type = $type->getWrappedType();
130+
}
131+
127132
if ($type instanceof ObjectType && !$type instanceof EnumType) {
128133
$typeString = (string) $type;
129134
$className = $type->getClassName();
@@ -133,7 +138,7 @@ private function createDataModel(Type $type, DataAccessorInterface $accessor, ar
133138
}
134139

135140
$context['generated_classes'][$typeString] = true;
136-
$propertiesMetadata = $this->propertyMetadataLoader->load($className, $options, ['original_type' => $type] + $context);
141+
$propertiesMetadata = $this->propertyMetadataLoader->load($className, $options, $context);
137142

138143
try {
139144
$classReflection = new \ReflectionClass($className);

0 commit comments

Comments
 (0)