diff --git a/composer.json b/composer.json index 100b230..14997fc 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,8 @@ "phpoption/phpoption": "^1.9" }, "require-dev": { - "phpunit/phpunit": "^11" + "phpunit/phpunit": "^11", + "square/pjson": "^0.5.0" }, "autoload": { "psr-4": { @@ -34,6 +35,7 @@ }, "autoload-dev": { "psr-4": { + "Test\\": "./tests", "Test\\Unit\\": "./tests/unit", "Test\\Integration\\": "./tests/integration", "Test\\Integration\\PostgreSQL\\": "./tests/integration/postgresql", diff --git a/composer.lock b/composer.lock index 1656c67..fa46b10 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "71593fca8aa32b0cd963eb52bad1a34e", + "content-hash": "dc897c0ab21d662a65b3818331edd2f2", "packages": [ { "name": "netresearch/jsonmapper", @@ -372,16 +372,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "11.0.3", + "version": "11.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "7e35a2cbcabac0e6865fd373742ea432a3c34f92" + "reference": "4dc2b7a606073f0fb80da09842ffb068b627c38f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e35a2cbcabac0e6865fd373742ea432a3c34f92", - "reference": "7e35a2cbcabac0e6865fd373742ea432a3c34f92", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/4dc2b7a606073f0fb80da09842ffb068b627c38f", + "reference": "4dc2b7a606073f0fb80da09842ffb068b627c38f", "shasum": "" }, "require": { @@ -438,7 +438,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.3" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.4" }, "funding": [ { @@ -446,7 +446,7 @@ "type": "github" } ], - "time": "2024-03-12T15:35:40+00:00" + "time": "2024-06-29T08:26:25+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1716,6 +1716,54 @@ ], "time": "2024-02-02T06:10:47+00:00" }, + { + "name": "square/pjson", + "version": "v0.5.0", + "source": { + "type": "git", + "url": "https://github.com/square/pjson.git", + "reference": "cf9f9a7810ad7287b30658f60c0bbbba80217319" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/square/pjson/zipball/cf9f9a7810ad7287b30658f60c0bbbba80217319", + "reference": "cf9f9a7810ad7287b30658f60c0bbbba80217319", + "shasum": "" + }, + "require": { + "php": ">=8.0" + }, + "require-dev": { + "orchestra/testbench": "^7.11", + "phpstan/phpstan": "^1.8", + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "^3.7", + "symfony/var-dumper": "^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Square\\Pjson\\": "src/", + "Square\\Pjson\\Tests\\": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Seb", + "email": "sebastien@squareup.com" + } + ], + "description": "Library for JSON <=> PHP model serialization / deserialization. Deserialize JSON directly into your object model PHP classes.", + "support": { + "issues": "https://github.com/square/pjson/issues", + "source": "https://github.com/square/pjson/tree/v0.5.0" + }, + "time": "2024-03-15T18:19:22+00:00" + }, { "name": "theseer/tokenizer", "version": "1.2.3", diff --git a/src/Mapper/DocumentMapper.php b/src/Mapper/DocumentMapper.php index a4d20ac..c6df7bd 100644 --- a/src/Mapper/DocumentMapper.php +++ b/src/Mapper/DocumentMapper.php @@ -3,8 +3,8 @@ namespace BitBadger\PDODocument\Mapper; use BitBadger\PDODocument\DocumentException; +use Exception; use JsonMapper; -use JsonMapper_Exception; /** * Map domain class instances from JSON documents @@ -32,12 +32,15 @@ class DocumentMapper implements Mapper public function map(array $result): mixed { try { + if (method_exists($this->className, 'fromJsonString')) { + return $this->className::fromJsonString($result[$this->fieldName]); + } $json = json_decode($result[$this->fieldName]); if (is_null($json)) { throw new DocumentException("Could not map document for $this->className: " . json_last_error_msg()); } return (new JsonMapper())->map($json, $this->className); - } catch (JsonMapper_Exception $ex) { + } catch (Exception $ex) { throw new DocumentException("Could not map document for $this->className", previous: $ex); } } diff --git a/src/Parameters.php b/src/Parameters.php index 7b63715..d3cc999 100644 --- a/src/Parameters.php +++ b/src/Parameters.php @@ -27,7 +27,10 @@ class Parameters */ public static function json(string $name, object|array $document): array { - return [$name => json_encode($document, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)]; + return [$name => match (is_object($document) && method_exists($document, 'toJson')) { + true => $document->toJson(), + false => json_encode($document, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) + }]; } /** diff --git a/tests/PjsonDocument.php b/tests/PjsonDocument.php new file mode 100644 index 0000000..79c2cd0 --- /dev/null +++ b/tests/PjsonDocument.php @@ -0,0 +1,16 @@ +value; + } + + public static function fromJsonData($jd, array|string $path = []): static + { + return new static($jd); + } +} diff --git a/tests/unit/Mapper/DocumentMapperTest.php b/tests/unit/Mapper/DocumentMapperTest.php index 691dedf..4534646 100644 --- a/tests/unit/Mapper/DocumentMapperTest.php +++ b/tests/unit/Mapper/DocumentMapperTest.php @@ -6,6 +6,7 @@ use BitBadger\PDODocument\{DocumentException, Field}; use BitBadger\PDODocument\Mapper\DocumentMapper; use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; +use Test\{PjsonDocument, PjsonId}; // ** Test class hierarchy for serialization ** @@ -48,10 +49,28 @@ class DocumentMapperTest extends TestCase $this->assertEquals('tester', $doc->subDoc->name, 'Sub-document name not filled correctly'); } + #[TestDox('Map succeeds with valid JSON for pjson class')] + public function testMapSucceedsWithValidJSONForPjsonClass(): void + { + $doc = (new DocumentMapper(PjsonDocument::class))->map(['data' => '{"id":"seven","name":"bob","num_value":8}']); + $this->assertNotNull($doc, 'The document should not have been null'); + $this->assertEquals(new PjsonId('seven'), $doc->id, 'ID not filled correctly'); + $this->assertEquals('bob', $doc->name, 'Name not filled correctly'); + $this->assertEquals(8, $doc->numValue, 'Numeric value not filled correctly'); + $this->assertFalse(isset($doc->skipped), 'Non-JSON field has not been set'); + } + #[TestDox('Map fails with invalid JSON')] public function testMapFailsWithInvalidJSON(): void { $this->expectException(DocumentException::class); (new DocumentMapper(TestDocument::class))->map(['data' => 'this is not valid']); } + + #[TestDox('Map fails with invalid JSON for pjson class')] + public function testMapFailsWithInvalidJSONForPjsonClass(): void + { + $this->expectException(DocumentException::class); + (new DocumentMapper(PjsonDocument::class))->map(['data' => 'not even close']); + } } diff --git a/tests/unit/ParametersTest.php b/tests/unit/ParametersTest.php index 9ef2002..03e7134 100644 --- a/tests/unit/ParametersTest.php +++ b/tests/unit/ParametersTest.php @@ -5,6 +5,8 @@ namespace Test\Unit; use BitBadger\PDODocument\{Configuration, DocumentException, Field, Mode, Parameters}; use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; +use stdClass; +use Test\{PjsonDocument, PjsonId}; /** * Unit tests for the Parameters class @@ -24,13 +26,31 @@ class ParametersTest extends TestCase $this->assertEquals([':id' => '7'], Parameters::id(7), 'ID parameter not constructed correctly'); } - public function testJsonSucceeds(): void + public function testJsonSucceedsForArray(): void { $this->assertEquals([':it' => '{"id":18,"url":"https://www.unittest.com"}'], Parameters::json(':it', ['id' => 18, 'url' => 'https://www.unittest.com']), 'JSON parameter not constructed correctly'); } + #[TestDox('json succeeds for stdClass')] + public function testJsonSucceedsForStdClass(): void + { + $obj = new stdClass(); + $obj->id = 19; + $obj->url = 'https://testhere.info'; + $this->assertEquals([':it' => '{"id":19,"url":"https://testhere.info"}'], Parameters::json(':it', $obj), + 'JSON parameter not constructed correctly'); + } + + public function testJsonSucceedsForPjsonClass(): void + { + $this->assertEquals([':it' => '{"id":"999","name":"a test","num_value":98}'], + Parameters::json(':it', new PjsonDocument(new PjsonId('999'), 'a test', 98, 'nothing')), + 'JSON parameter not constructed correctly'); + } + + public function testNameFieldsSucceeds(): void { $named = Parameters::nameFields([Field::EQ('it', 17), Field::EQ('also', 22, ':also'), Field::EQ('other', 24)]);