Skip to content

Commit

Permalink
Merge pull request #1 from Lendable/lib
Browse files Browse the repository at this point in the history
Initial implementation
  • Loading branch information
ben-challis authored Nov 27, 2018
2 parents a942c03 + df46a34 commit 1b61f76
Show file tree
Hide file tree
Showing 10 changed files with 255 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/vendor
phpunit.xml
composer.lock
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
Lendable JSON
=============
Provides an object oriented interface for handling json in php.

Instead of failing silently, this lib will throw exceptions on json errors.

## Installation
```bash
composer require lendable/json-serializer
```
28 changes: 28 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "lendable/json-serializer",
"description": "JSON serializer/deserializer with an OOP interface",
"type": "library",
"authors": [
{
"name": "Lendable Ltd",
"email": "[email protected]"
}
],
"autoload": {
"psr-4": {
"Lendable\\Json\\": "lib/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\Lendable\\Json\\Unit\\": "tests/unit/"
}
},
"require": {
"php": ">=7.1",
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "^7.4"
}
}
21 changes: 21 additions & 0 deletions lib/DeserializationFailed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Lendable\Json;

final class DeserializationFailed extends \RuntimeException implements Failure
{
public function __construct(int $errorCode, string $errorMessage, ?\Throwable $previous = null)
{
parent::__construct(
\sprintf(
'Failed to deserialize data from JSON. Error code: %d, error message: %s.',
$errorCode,
$errorMessage
),
$errorCode,
$previous
);
}
}
9 changes: 9 additions & 0 deletions lib/Failure.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace Lendable\Json;

interface Failure extends \Throwable
{
}
13 changes: 13 additions & 0 deletions lib/InvalidDeserializedData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Lendable\Json;

class InvalidDeserializedData extends \RuntimeException implements Failure
{
public function __construct(string $unexpectedType)
{
parent::__construct(sprintf('Expected array when deserializing JSON, got "%s".', $unexpectedType));
}
}
21 changes: 21 additions & 0 deletions lib/SerializationFailed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Lendable\Json;

final class SerializationFailed extends \RuntimeException implements Failure
{
public function __construct(int $errorCode, string $errorMessage, ?\Throwable $previous = null)
{
parent::__construct(
\sprintf(
'Failed to serialize data to JSON. Error code: %d, error message: %s.',
$errorCode,
$errorMessage
),
$errorCode,
$previous
);
}
}
43 changes: 43 additions & 0 deletions lib/Serializer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace Lendable\Json;

final class Serializer
{
/**
* @throws SerializationFailure
*/
public function serialize(array $data): string
{
$serialized = \json_encode($data);

if (\json_last_error() !== JSON_ERROR_NONE) {
throw new SerializationFailed(\json_last_error(), \json_last_error_msg());
}

\assert(\is_string($serialized));

return $serialized;
}

/**
* @throws DeserializationFailure
* @throws InvalidDeserializedData
*/
public function deserialize(string $json): array
{
$data = \json_decode($json, true);

if (\json_last_error() !== JSON_ERROR_NONE) {
throw new DeserializationFailed(\json_last_error(), \json_last_error_msg());
}

if (!\is_array($data)) {
throw new InvalidDeserializedData(\gettype($data));
}

return $data;
}
}
26 changes: 26 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
bootstrap="vendor/autoload.php">

<php>
<ini name="error_reporting" value="E_ALL" />
</php>

<testsuites>
<testsuite name="unit">
<directory>./tests/unit/</directory>
</testsuite>
</testsuites>

<filter>
<whitelist>
<directory>./lib/</directory>
</whitelist>
</filter>

</phpunit>
83 changes: 83 additions & 0 deletions tests/unit/SerializerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php

declare(strict_types=1);

namespace Tests\Lendable\Json\Unit;

use Lendable\Json\DeserializationFailed;
use Lendable\Json\InvalidDeserializedData;
use Lendable\Json\SerializationFailed;
use Lendable\Json\Serializer;
use PHPUnit\Framework\TestCase;

/**
* @covers \Lendable\Json\Serializer
* @covers \Lendable\Json\SerializationFailed
* @covers \Lendable\Json\DeserializationFailed
* @covers \Lendable\Json\InvalidDeserializedData
*/
final class SerializerTest extends TestCase
{
/**
* @var Serializer
*/
private $serializer;

/**
* @test
*/
public function it_can_serialize_an_array_of_scalars_to_json(): void
{
$result = $this->serializer->serialize(['foo' => 'bar', 'baz' => [1.03, true, 'foobar']]);

$this->assertSame('{"foo":"bar","baz":[1.03,true,"foobar"]}', $result);
}

/**
* @test
*/
public function it_throws_when_serializing_if_an_error_encountered(): void
{
$this->expectException(SerializationFailed::class);
$this->expectExceptionMessage('Failed to serialize data to JSON. Error code: 5, error message: Malformed UTF-8 characters, possibly incorrectly encoded.');

$this->serializer->serialize(["\xf0\x28\x8c\xbc" => 'bar']);
}

/**
* @test
*/
public function it_can_deserialize_from_a_json_string_to_php_scalars(): void
{
$result = $this->serializer->deserialize('{"foo":"bar","baz":[1.03,true,"foobar"]}');

$this->assertSame(['foo' => 'bar', 'baz' => [1.03, true, 'foobar']], $result);
}

/**
* @test
*/
public function it_throws_when_deserializing_if_an_error_encountered(): void
{
$this->expectException(DeserializationFailed::class);
$this->expectExceptionMessage('Failed to deserialize data from JSON. Error code: 4, error message: Syntax error.');

$this->serializer->deserialize('{"unclosed":"bad","object":"json"');
}

/**
* @test
*/
public function it_throws_when_deserializing_if_the_result_is_not_an_array(): void
{
$this->expectExceptionMessage(InvalidDeserializedData::class);
$this->expectExceptionMessage('Expected array when deserializing JSON, got "boolean".');

$this->serializer->deserialize('true');
}

protected function setUp(): void
{
$this->serializer = new Serializer();
}
}

0 comments on commit 1b61f76

Please sign in to comment.