Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Adds default handling for un-serializable data in toJson method. #16

Merged
merged 1 commit into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/tests export-ignore
/vendor export-ignore

/README.md export-ignore
/LICENSE export-ignore
/Makefile export-ignore
/phpmd.xml export-ignore
/phpunit.xml export-ignore
/phpstan.neon.dist export-ignore
/infection.json.dist export-ignore

/.github export-ignore
/.gitignore export-ignore
/.gitattributes export-ignore
13 changes: 9 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
"psr",
"json",
"array",
"psr-4",
"psr-12",
"serializer",
"tiny-blocks"
],
Expand All @@ -21,6 +19,10 @@
"homepage": "https://github.com/gustavofreze"
}
],
"support": {
"issues": "https://github.com/tiny-blocks/serializer/issues",
"source": "https://github.com/tiny-blocks/serializer"
},
"config": {
"sort-packages": true,
"allow-plugins": {
Expand All @@ -41,22 +43,25 @@
"php": "^8.2"
},
"require-dev": {
"infection/infection": "^0.29",
"phpmd/phpmd": "^2.15",
"phpstan/phpstan": "^1",
"phpunit/phpunit": "^11",
"infection/infection": "^0.29",
"squizlabs/php_codesniffer": "^3.10"
},
"scripts": {
"phpcs": "phpcs --standard=PSR12 --extensions=php ./src",
"phpmd": "phpmd ./src text phpmd.xml --suffixes php --ignore-violations-on-exit",
"phpstan": "phpstan analyse -c phpstan.neon.dist --quiet --no-progress",
"test": "phpunit --log-junit=report/coverage/junit.xml --coverage-xml=report/coverage/coverage-xml --coverage-html=report/coverage/coverage-html tests",
"fix-style": "phpcbf ./src --extensions=php",
"test-mutation": "infection --only-covered --logger-html=report/coverage/mutation-report.html --coverage=report/coverage --min-msi=100 --min-covered-msi=100 --threads=4",
"test-no-coverage": "phpunit --no-coverage",
"test-mutation-no-coverage": "infection --only-covered --min-msi=100 --threads=4",
"review": [
"@phpcs",
"@phpmd"
"@phpmd",
"@phpstan"
],
"tests": [
"@test",
Expand Down
6 changes: 3 additions & 3 deletions infection.json.dist
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"timeout": 10,
"testFramework": "phpunit",
"tmpDir": "report/",
"tmpDir": "report/infection/",
"source": {
"directories": [
"src"
]
},
"logs": {
"text": "report/logs/infection-text.log",
"summary": "report/logs/infection-summary.log"
"text": "report/infection/logs/infection-text.log",
"summary": "report/infection/logs/infection-summary.log"
},
"mutators": {
"@default": true,
Expand Down
11 changes: 11 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
parameters:
paths:
- src
level: 9
tmpDir: report/phpstan
ignoreErrors:
- '#method toArray#'
- '#object_or_class#'
- '#with no value type specified in#'
- '#return type has no value type specified#'
reportUnmatchedIgnoredErrors: false
2 changes: 1 addition & 1 deletion src/Internal/ArraySerializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public function __construct(private iterable $iterable)
public function toArray(SerializeKeys $serializeKeys = SerializeKeys::PRESERVE): array
{
$mapper = new Mapper(serializeKeys: $serializeKeys);
$shouldPreserveKeys = $serializeKeys && $serializeKeys->shouldPreserveKeys();
$shouldPreserveKeys = $serializeKeys->shouldPreserveKeys();

$elements = is_array($this->iterable)
? $this->iterable
Expand Down
10 changes: 8 additions & 2 deletions src/Internal/JsonSerializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@
public function serialize(array $data): string
{
$isSingleItem = count($data) === 1;
$dataToSerialize = $isSingleItem ? $data[0] : $data;
$dataToSerialize = $isSingleItem ? ($data[0] ?? null) : $data;

return json_encode($dataToSerialize, JSON_PRESERVE_ZERO_FRACTION);
$json = json_encode($dataToSerialize, JSON_PRESERVE_ZERO_FRACTION);

if (!is_string($json) || $json === 'null') {
return $isSingleItem ? '{}' : '[]';
}

return $json;
}
}
2 changes: 1 addition & 1 deletion src/Internal/Mappers/Mapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ final class Mapper
{
private ValueMapper $valueMapper;

public function __construct(?SerializeKeys $serializeKeys)
public function __construct(SerializeKeys $serializeKeys)
{
$this->valueMapper = new ValueMapper(serializeKeys: $serializeKeys);
}
Expand Down
20 changes: 16 additions & 4 deletions src/Serializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,29 @@ interface Serializer
/**
* Serializes the object to a JSON string.
*
* @param SerializeKeys $serializeKeys Optional parameter to define how keys
* should be serialized (default: PRESERVE).
* The key serialization behavior can be customized using the `SerializeKeys` enum:
* - `SerializeKeys::DISCARD`: Discards the array keys.
* - `SerializeKeys::PRESERVE`: Preserves the array keys.
*
* By default, `SerializeKeys::PRESERVE` is used.
*
* @param SerializeKeys $serializeKeys Optional parameter to define whether array keys
* should be preserved or discarded.
* @return string The JSON string representing the object.
*/
public function toJson(SerializeKeys $serializeKeys = SerializeKeys::PRESERVE): string;

/**
* Converts the object to an array.
*
* @param SerializeKeys $serializeKeys Optional parameter to define how keys
* should be serialized (default: PRESERVE).
* The key serialization behavior can be customized using the `SerializeKeys` enum:
* - `SerializeKeys::DISCARD`: Discards the array keys.
* - `SerializeKeys::PRESERVE`: Preserves the array keys.
*
* By default, `SerializeKeys::PRESERVE` is used.
*
* @param SerializeKeys $serializeKeys Optional parameter to define whether array keys
* should be preserved or discarded.
* @return array<Key, Value> The array representation of the object.
*/
public function toArray(SerializeKeys $serializeKeys = SerializeKeys::PRESERVE): array;
Expand Down
12 changes: 12 additions & 0 deletions tests/IterableSerializerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,18 @@ public function testSerializeToArray(iterable $iterable, array $expected): void
self::assertSame($expected, $actual);
}

public function testSerializeMultipleInvalidItemsReturnsEmptyJsonArray(): void
{
/** @Given multiple invalid items (e.g., functions that cannot be serialized) */
$serializer = new IterableSerializer(iterable: [fn(): null => null, fn(): null => null]);

/** @When attempting to serialize the invalid items */
$actual = $serializer->toJson();

/** @Then the output should be an empty JSON array */
self::assertSame('[[],[]]', $actual);
}

public static function toJsonDataProvider(): array
{
$spTimeZone = new DateTimeZone('America/Sao_Paulo');
Expand Down
18 changes: 18 additions & 0 deletions tests/Models/Serializable/Service.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace TinyBlocks\Serializer\Models\Serializable;

use Closure;
use TinyBlocks\Serializer\Serializer;
use TinyBlocks\Serializer\SerializerAdapter;

final class Service implements Serializer
{
use SerializerAdapter;

public function __construct(public Closure $action)
{
}
}
13 changes: 13 additions & 0 deletions tests/SerializerAdapterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use TinyBlocks\Serializer\Models\Serializable\Address;
use TinyBlocks\Serializer\Models\Serializable\Addresses;
use TinyBlocks\Serializer\Models\Serializable\Country;
use TinyBlocks\Serializer\Models\Serializable\Service;
use TinyBlocks\Serializer\Models\Serializable\Shipping;
use TinyBlocks\Serializer\Models\Serializable\State;

Expand All @@ -34,6 +35,18 @@ public function testSerializeToJson(Shipping $shipping, string $expected): void
self::assertJsonStringEqualsJsonString($expected, $actual);
}

public function testSerializeSingleInvalidItemReturnsEmptyJsonObject(): void
{
/** @Given a single invalid item (e.g., a function that cannot be serialized) */
$service = new Service(action: fn(): int => 0);

/** @When attempting to serialize the invalid item */
$actual = $service->toJson();

/** @Then the output should be an empty JSON object */
self::assertSame('{}', $actual);
}

public static function shippingDataProviderForArray(): array
{
$shippingWithNoAddresses = new Shipping(id: 1, addresses: new Addresses());
Expand Down
Loading