diff --git a/src/JSON.php b/src/JSON.php new file mode 100644 index 0000000..70cde82 --- /dev/null +++ b/src/JSON.php @@ -0,0 +1,78 @@ + $depth + * + * @throws JsonException + */ + public static function decode(#[Language('JSON')] string $json, ?bool $associative = null, int $depth = 512, int $flags = 0): mixed + { + return json_decode($json, $associative, $depth, JSON_THROW_ON_ERROR | $flags); + } + + /** + * @param int<1, 2147483647> $depth + */ + public static function decodeSilently(#[Language('JSON')] string $json, ?bool $associative = null, int $depth = 512, int $flags = 0): mixed + { + try { + return self::decode($json, $associative, $depth, $flags); + } catch (JsonException) { + return null; + } + } + + /** + * @param int<1, 2147483647> $depth + * + * @throws JsonException + * + * @return array + */ + public static function decodeAsArray(#[Language('JSON')] string $json, int $depth = 512, int $flags = 0): array + { + return is_array($decoded = self::decode($json, false, $depth, $flags)) + ? $decoded + : throw new JsonException(sprintf('Input JSON %s is expected to be an array. Got %s instead.', $json, gettype($decoded))); + } + + /** + * @param int<1, 2147483647> $depth + * + * @throws JsonException + */ + public static function decodeAsObject(#[Language('JSON')] string $json, int $depth = 512, int $flags = 0): object + { + return is_object($decoded = self::decode($json, false, $depth, $flags)) + ? $decoded + : throw new JsonException(sprintf('Input JSON %s is expected to be an object. Got %s instead.', $json, gettype($decoded))); + } + + /** + * @param int<1, 2147483647> $depth + * @param int-mask-of<0|1048576> $flags + */ + public static function validate(#[Language('JSON')] string $json, int $depth = 512, int $flags = 0): bool + { + return json_validate($json, $depth, $flags); + } +} diff --git a/src/JsonDecoder.php b/src/JsonDecoder.php index 70e8267..b266cfc 100644 --- a/src/JsonDecoder.php +++ b/src/JsonDecoder.php @@ -7,6 +7,9 @@ use Artemeon\Support\Exception\InvalidJsonFormatException; use JsonException; +/** + * @deprecated Use {@see JSON} instead. + */ final class JsonDecoder { public static function decode(string $json, bool $assoc = true, int $flags = 0): mixed diff --git a/tests/Unit/JSONTest.php b/tests/Unit/JSONTest.php new file mode 100644 index 0000000..f7be673 --- /dev/null +++ b/tests/Unit/JSONTest.php @@ -0,0 +1,87 @@ +toBe($expected); + }) + ->with([ + [['foo' => 'bar'], '{"foo":"bar"}'], + [['foo' => 'bar', 'baz' => 'qux'], '{"foo":"bar","baz":"qux"}'], + [true, 'true'], + [false, 'false'], + [null, 'null'], + [123, '123'], + [123.456, '123.456'], + ['foo', '"foo"'], + ['foo"bar', '"foo\\"bar"'], + ]); +}); + +describe('Simple JSON Decoding', function (): void { + it('should decode data', function (): void { + expect(JSON::decode('{"foo": "bar"}', true)) + ->toBe(['foo' => 'bar']); + }); + + it('should throw with invalid data', function (): void { + JSON::decode('lol'); + })->throws(JsonException::class); + + it('should return null and not throw with invalid data', function (): void { + expect(JSON::decodeSilently('lol')) + ->toBeNull(); + }); +}); + +describe('JSON Validation', function (): void { + it('should validate data', function (): void { + expect(JSON::validate('{"foo": "bar"}')) + ->toBeTrue() + ->and(JSON::validate('lol')) + ->toBeFalse(); + }); +}); + +$nonArrayAndObjectJsonData = [ + ['true', 'boolean'], + ['false', 'boolean'], + ['"foo"', 'string'], + ['123', 'integer'], + ['123.456', 'double'], + ['null', 'NULL'], +]; + +describe('JSON Array Decoding', function () use ($nonArrayAndObjectJsonData): void { + it('should decode as an array', function (): void { + expect(JSON::decodeAsArray('["foo", "bar"]')) + ->toBe(['foo', 'bar']); + }); + + it('should throw with invalid array', function (string $json, string $type): void { + expect(fn (): array => JSON::decodeAsArray($json)) + ->toThrow(JsonException::class, sprintf('Input JSON %s is expected to be an array. Got %s instead.', $json, $type)); + })->with([ + ['{"foo": "bar"}', 'object'], + ...$nonArrayAndObjectJsonData, + ]); +}); + +describe('JSON Object Decoding', function () use ($nonArrayAndObjectJsonData): void { + it('should decode as an object', function (): void { + expect(JSON::decodeAsObject('{"foo": "bar"}')) + ->toEqual((object) ['foo' => 'bar']); + }); + + it('should throw with invalid object', function (string $json, string $type): void { + expect(fn (): object => JSON::decodeAsObject($json)) + ->toThrow(JsonException::class, sprintf('Input JSON %s is expected to be an object. Got %s instead.', $json, $type)); + })->with([ + ['["foo", "bar"]', 'array'], + ...$nonArrayAndObjectJsonData, + ]); +});