Issues (224)

src/Json/Json.php (1 issue)

Labels
Severity
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the box project.
7
 *
8
 * (c) Kevin Herrera <[email protected]>
9
 *     Théo Fidry <[email protected]>
10
 *
11
 * This source file is subject to the MIT license that is bundled
12
 * with this source code in the file LICENSE.
13
 */
14
15
namespace KevinGH\Box\Json;
16
17
use Fidry\FileSystem\FS;
18
use JsonSchema\Validator;
19
use Seld\JsonLint\JsonParser;
20
use Seld\JsonLint\ParsingException;
21
use stdClass;
22
use function implode;
23
use function json_decode;
24
use function json_last_error;
25
use const JSON_ERROR_NONE;
26
use const JSON_ERROR_UTF8;
27
28
/**
29
 * @private
30
 */
31
final readonly class Json
0 ignored issues
show
A parse error occurred: Syntax error, unexpected T_READONLY, expecting T_CLASS on line 31 at column 6
Loading history...
32
{
33
    private JsonParser $linter;
34
35
    public function __construct()
36
    {
37
        $this->linter = new JsonParser();
38
    }
39
40
    /**
41
     * @throws ParsingException
42
     */
43
    public function lint(string $json): void
44
    {
45
        $result = $this->linter->lint($json);
46
47
        if ($result instanceof ParsingException) {
48
            throw $result;
49
        }
50
    }
51
52
    /**
53
     * @throws ParsingException
54
     */
55
    public function decode(string $json, bool $assoc = false): array|stdClass
56
    {
57
        $data = json_decode($json, $assoc);
58
59
        if (JSON_ERROR_NONE !== ($error = json_last_error())) {
60
            // Swallow the UTF-8 error and relies on the lint instead otherwise
61
            if (JSON_ERROR_UTF8 === $error) {
62
                throw new ParsingException('JSON decoding failed: Malformed UTF-8 characters, possibly incorrectly encoded');
63
            }
64
65
            $this->lint($json);
66
        }
67
68
        return false === $assoc ? (object) $data : $data;   // If JSON is an empty JSON json_decode returns an empty
69
        // array instead of an stdClass instance
70
    }
71
72
    /**
73
     * @throws ParsingException
74
     */
75
    public function decodeFile(string $file, bool $assoc = false): array|stdClass
76
    {
77
        $json = FS::getFileContents($file);
78
79
        return $this->decode($json, $assoc);
80
    }
81
82
    /**
83
     * Validates the decoded JSON data.
84
     *
85
     * @param string   $file   The JSON file
86
     * @param stdClass $json   The decoded JSON data
87
     * @param stdClass $schema The JSON schema
88
     *
89
     * @throws JsonValidationException If the JSON data failed validation
90
     */
91
    public function validate(string $file, stdClass $json, stdClass $schema): void
92
    {
93
        $validator = new Validator();
94
        $validator->check($json, $schema);
95
96
        if (!$validator->isValid()) {
97
            $errors = [];
98
99
            foreach ($validator->getErrors() as $error) {
100
                $errors[] = ($error['property'] ? $error['property'].' : ' : '').$error['message'];
101
            }
102
103
            $message = [] !== $errors
104
                ? "\"{$file}\" does not match the expected JSON schema:\n  - ".implode("\n  - ", $errors)
105
                : "\"{$file}\" does not match the expected JSON schema.";
106
107
            throw new JsonValidationException($message, $file, $errors);
108
        }
109
    }
110
}
111