Skip to content

Commit 697da0b

Browse files
DjordyKoertnicolas-grekas
authored andcommitted
[JsonStreamer] fix invalid json output for list of self
1 parent f5f8723 commit 697da0b

11 files changed

+218
-1
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Fixtures\Model;
4+
5+
namespace Symfony\Component\JsonStreamer\Tests\Fixtures\Model;
6+
7+
class DummyWithNestedDictDummies
8+
{
9+
/** @var array<string, DummyWithNestedDictDummies> */
10+
public array $dummies = [];
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Fixtures\Model;
4+
5+
namespace Symfony\Component\JsonStreamer\Tests\Fixtures\Model;
6+
7+
class DummyWithNestedListDummies
8+
{
9+
/** @var DummyWithNestedListDummies[] */
10+
public array $dummies = [];
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Symfony\Component\JsonStreamer\Tests\Fixtures\Model;
4+
5+
class SelfReferencingDummyDict
6+
{
7+
/**
8+
* @var array<string, self>
9+
*/
10+
public array $items = [];
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Symfony\Component\JsonStreamer\Tests\Fixtures\Model;
4+
5+
class SelfReferencingDummyList
6+
{
7+
/**
8+
* @var self[]
9+
*/
10+
public array $items = [];
11+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
/**
4+
* @param Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNestedDictDummies $data
5+
*/
6+
return static function (mixed $data, \Psr\Container\ContainerInterface $valueTransformers, array $options): \Traversable {
7+
$generators['Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNestedDictDummies'] = static function ($data, $depth) use ($valueTransformers, $options, &$generators) {
8+
if ($depth >= 512) {
9+
throw new \Symfony\Component\JsonStreamer\Exception\NotEncodableValueException('Maximum stack depth exceeded');
10+
}
11+
$prefix1 = '';
12+
yield "{{$prefix1}\"dummies\":";
13+
yield "{";
14+
$prefix2 = '';
15+
foreach ($data->dummies as $key1 => $value1) {
16+
$key1 = \substr(\json_encode($key1), 1, -1);
17+
yield "{$prefix2}\"{$key1}\":";
18+
yield from $generators['Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNestedDictDummies']($value1, $depth + 1);
19+
$prefix2 = ',';
20+
}
21+
yield "}}";
22+
};
23+
try {
24+
yield from $generators['Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNestedDictDummies']($data, 0);
25+
} catch (\JsonException $e) {
26+
throw new \Symfony\Component\JsonStreamer\Exception\NotEncodableValueException($e->getMessage(), 0, $e);
27+
}
28+
};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
/**
4+
* @param Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNestedListDummies $data
5+
*/
6+
return static function (mixed $data, \Psr\Container\ContainerInterface $valueTransformers, array $options): \Traversable {
7+
$generators['Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNestedListDummies'] = static function ($data, $depth) use ($valueTransformers, $options, &$generators) {
8+
if ($depth >= 512) {
9+
throw new \Symfony\Component\JsonStreamer\Exception\NotEncodableValueException('Maximum stack depth exceeded');
10+
}
11+
$prefix1 = '';
12+
yield "{{$prefix1}\"dummies\":";
13+
yield "[";
14+
$prefix2 = '';
15+
foreach ($data->dummies as $value1) {
16+
yield "{$prefix2}";
17+
yield from $generators['Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNestedListDummies']($value1, $depth + 1);
18+
$prefix2 = ',';
19+
}
20+
yield "]}";
21+
};
22+
try {
23+
yield from $generators['Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNestedListDummies']($data, 0);
24+
} catch (\JsonException $e) {
25+
throw new \Symfony\Component\JsonStreamer\Exception\NotEncodableValueException($e->getMessage(), 0, $e);
26+
}
27+
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
/**
4+
* @param Symfony\Component\JsonStreamer\Tests\Fixtures\Model\SelfReferencingDummyDict $data
5+
*/
6+
return static function (mixed $data, \Psr\Container\ContainerInterface $valueTransformers, array $options): \Traversable {
7+
$generators['Symfony\Component\JsonStreamer\Tests\Fixtures\Model\SelfReferencingDummyDict'] = static function ($data, $depth) use ($valueTransformers, $options, &$generators) {
8+
if ($depth >= 512) {
9+
throw new \Symfony\Component\JsonStreamer\Exception\NotEncodableValueException('Maximum stack depth exceeded');
10+
}
11+
$prefix1 = '';
12+
yield "{{$prefix1}\"items\":";
13+
yield "{";
14+
$prefix2 = '';
15+
foreach ($data->items as $key1 => $value1) {
16+
$key1 = \substr(\json_encode($key1), 1, -1);
17+
yield "{$prefix2}\"{$key1}\":";
18+
yield from $generators['Symfony\Component\JsonStreamer\Tests\Fixtures\Model\SelfReferencingDummyDict']($value1, $depth + 1);
19+
$prefix2 = ',';
20+
}
21+
yield "}}";
22+
};
23+
try {
24+
yield from $generators['Symfony\Component\JsonStreamer\Tests\Fixtures\Model\SelfReferencingDummyDict']($data, 0);
25+
} catch (\JsonException $e) {
26+
throw new \Symfony\Component\JsonStreamer\Exception\NotEncodableValueException($e->getMessage(), 0, $e);
27+
}
28+
};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
/**
4+
* @param Symfony\Component\JsonStreamer\Tests\Fixtures\Model\SelfReferencingDummyList $data
5+
*/
6+
return static function (mixed $data, \Psr\Container\ContainerInterface $valueTransformers, array $options): \Traversable {
7+
$generators['Symfony\Component\JsonStreamer\Tests\Fixtures\Model\SelfReferencingDummyList'] = static function ($data, $depth) use ($valueTransformers, $options, &$generators) {
8+
if ($depth >= 512) {
9+
throw new \Symfony\Component\JsonStreamer\Exception\NotEncodableValueException('Maximum stack depth exceeded');
10+
}
11+
$prefix1 = '';
12+
yield "{{$prefix1}\"items\":";
13+
yield "[";
14+
$prefix2 = '';
15+
foreach ($data->items as $value1) {
16+
yield "{$prefix2}";
17+
yield from $generators['Symfony\Component\JsonStreamer\Tests\Fixtures\Model\SelfReferencingDummyList']($value1, $depth + 1);
18+
$prefix2 = ',';
19+
}
20+
yield "]}";
21+
};
22+
try {
23+
yield from $generators['Symfony\Component\JsonStreamer\Tests\Fixtures\Model\SelfReferencingDummyList']($data, 0);
24+
} catch (\JsonException $e) {
25+
throw new \Symfony\Component\JsonStreamer\Exception\NotEncodableValueException($e->getMessage(), 0, $e);
26+
}
27+
};

Tests/JsonStreamWriterTest.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,16 @@
2525
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithGenerics;
2626
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNameAttributes;
2727
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNestedArray;
28+
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNestedDictDummies;
29+
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNestedListDummies;
2830
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNullableProperties;
2931
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithPhpDoc;
3032
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithSyntheticProperties;
3133
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithUnionProperties;
3234
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithValueTransformerAttributes;
3335
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\SelfReferencingDummy;
36+
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\SelfReferencingDummyDict;
37+
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\SelfReferencingDummyList;
3438
use Symfony\Component\JsonStreamer\Tests\Fixtures\ValueTransformer\BooleanToStringValueTransformer;
3539
use Symfony\Component\JsonStreamer\Tests\Fixtures\ValueTransformer\DoubleIntAndCastToStringValueTransformer;
3640
use Symfony\Component\JsonStreamer\ValueTransformer\DateTimeToStringValueTransformer;
@@ -283,6 +287,56 @@ public function testWriteObjectWithSyntheticProperty()
283287
$this->assertSame('{"synthetic":true}', (string) $writer->write(new DummyWithSyntheticProperties(), Type::object(DummyWithSyntheticProperties::class)));
284288
}
285289

290+
public function testWriteNestedSelfList()
291+
{
292+
$dummy = new SelfReferencingDummyList();
293+
$dummy->items = [new SelfReferencingDummyList(), new SelfReferencingDummyList(), new SelfReferencingDummyList()];
294+
295+
$this->assertWritten(
296+
'{"items":[{"items":[]},{"items":[]},{"items":[]}]}',
297+
$dummy,
298+
Type::object(SelfReferencingDummyList::class)
299+
);
300+
301+
$dummy = new DummyWithNestedListDummies();
302+
$dummy->dummies = [new DummyWithNestedListDummies(), new DummyWithNestedListDummies(), new DummyWithNestedListDummies()];
303+
304+
$this->assertWritten(
305+
'{"dummies":[{"dummies":[]},{"dummies":[]},{"dummies":[]}]}',
306+
$dummy,
307+
Type::object(DummyWithNestedListDummies::class)
308+
);
309+
}
310+
311+
public function testWriteNestedSelfDict()
312+
{
313+
$dummy = new SelfReferencingDummyDict();
314+
$dummy->items = [
315+
'first' => new SelfReferencingDummyDict(),
316+
'second' => new SelfReferencingDummyDict(),
317+
'third' => new SelfReferencingDummyDict(),
318+
];
319+
320+
$this->assertWritten(
321+
'{"items":{"first":{"items":{}},"second":{"items":{}},"third":{"items":{}}}}',
322+
$dummy,
323+
Type::object(SelfReferencingDummyDict::class)
324+
);
325+
326+
$dummy = new DummyWithNestedDictDummies();
327+
$dummy->dummies = [
328+
'first' => new DummyWithNestedDictDummies(),
329+
'second' => new DummyWithNestedDictDummies(),
330+
'third' => new DummyWithNestedDictDummies(),
331+
];
332+
333+
$this->assertWritten(
334+
'{"dummies":{"first":{"dummies":{}},"second":{"dummies":{}},"third":{"dummies":{}}}}',
335+
$dummy,
336+
Type::object(DummyWithNestedDictDummies::class)
337+
);
338+
}
339+
286340
#[DataProvider('throwWhenMaxDepthIsReachedDataProvider')]
287341
public function testThrowWhenMaxDepthIsReached(Type $type, mixed $data)
288342
{

Tests/Write/StreamWriterGeneratorTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,15 @@
2727
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithDollarNamedProperties;
2828
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNameAttributes;
2929
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNestedArray;
30+
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNestedDictDummies;
31+
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNestedListDummies;
3032
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithOtherDummies;
3133
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithSyntheticProperties;
3234
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithUnionProperties;
3335
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithValueTransformerAttributes;
3436
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\SelfReferencingDummy;
37+
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\SelfReferencingDummyDict;
38+
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\SelfReferencingDummyList;
3539
use Symfony\Component\JsonStreamer\Tests\Fixtures\ValueTransformer\BooleanToStringValueTransformer;
3640
use Symfony\Component\JsonStreamer\Tests\Fixtures\ValueTransformer\DoubleIntAndCastToStringValueTransformer;
3741
use Symfony\Component\JsonStreamer\Tests\ServiceContainer;
@@ -99,6 +103,8 @@ public static function generatedStreamWriterDataProvider(): iterable
99103
yield ['nullable_object_list', Type::nullable(Type::list(Type::object(DummyWithNameAttributes::class)))];
100104
yield ['nested_list', Type::list(Type::object(DummyWithArray::class))];
101105
yield ['double_nested_list', Type::list(Type::object(DummyWithNestedArray::class))];
106+
yield ['object_with_nested_list_self', Type::object(DummyWithNestedListDummies::class)];
107+
yield ['object_with_nested_dict_self', Type::object(DummyWithNestedDictDummies::class)];
102108

103109
yield ['dict', Type::dict()];
104110
yield ['object_dict', Type::dict(Type::object(DummyWithNameAttributes::class))];
@@ -112,6 +118,8 @@ public static function generatedStreamWriterDataProvider(): iterable
112118
yield ['object_in_object', Type::object(DummyWithOtherDummies::class)];
113119
yield ['object_with_value_transformer', Type::object(DummyWithValueTransformerAttributes::class)];
114120
yield ['self_referencing_object', Type::object(SelfReferencingDummy::class)];
121+
yield ['self_referencing_object_list', Type::object(SelfReferencingDummyList::class)];
122+
yield ['self_referencing_object_dict', Type::object(SelfReferencingDummyDict::class)];
115123
yield ['object_with_dollar_named_properties', Type::object(DummyWithDollarNamedProperties::class)];
116124
yield ['object_with_synthetic_properties', Type::object(DummyWithSyntheticProperties::class), new SyntheticPropertyMetadataLoader()];
117125

0 commit comments

Comments
 (0)