Skip to content
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
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": "dev@lendable.co.uk"
}
],
"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();
}
}