Passed
Push — master ( f4e6fb...8cf48f )
by Alexander
11:31
created

Json::decode()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 6
c 1
b 0
f 0
nc 3
nop 4
dl 0
loc 11
ccs 7
cts 7
cp 1
crap 3
rs 10
1
<?php
2
namespace Yiisoft\Json {
3
    /**
4
     * Json is a helper class providing JSON data encoding and decoding.
5
     * It enhances the PHP built-in functions `json_encode()` and `json_decode()`
6
     * by throwing exceptions when decoding fails.
7
     */
8
    final class Json
9
    {
10
        private const ERRORS = [
11
            'JSON_ERROR_DEPTH' => 'Maximum stack depth exceeded',
12
            'JSON_ERROR_STATE_MISMATCH' => 'State mismatch (invalid or malformed JSON)',
13
            'JSON_ERROR_CTRL_CHAR' => 'Control character error, possibly incorrectly encoded',
14
            'JSON_ERROR_SYNTAX' => 'Syntax error',
15
            'JSON_ERROR_UTF8' => 'Malformed UTF-8 characters, possibly incorrectly encoded',
16
            'JSON_ERROR_RECURSION' => 'Recursion detected',
17
            'JSON_ERROR_INF_OR_NAN' => 'Inf and NaN cannot be JSON encoded',
18
            'JSON_ERROR_UNSUPPORTED_TYPE' => 'Type is not supported',
19
            'JSON_ERROR_INVALID_PROPERTY_NAME' => 'The decoded property name is invalid',
20
            'JSON_ERROR_UTF16' => 'Single unpaired UTF-16 surrogate in unicode escape',
21
        ];
22
23
        /**
24
         * Encodes the given value into a JSON string.
25
         *
26
         * Note that data encoded as JSON must be UTF-8 encoded according to the JSON specification.
27
         * You must ensure strings passed to this method have proper encoding before passing them.
28
         *
29
         * @param mixed $value the data to be encoded.
30
         * @param int $options the encoding options. For more details please refer to
31
         * <http://www.php.net/manual/en/function.json-encode.php>. Default is `JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR`.
32
         * @param int $depth the maximum depth.
33
         * @return string the encoding result.
34
         * @throws \JsonException if there is any encoding error.
35
         */
36 17
        public static function encode($value, int $options = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR, int $depth = 512): string
37
        {
38 17
            $shouldRethrowErrors = self::shouldRethrowErrors($options);
39
40 17
            $value = self::processData($value);
41
42 17
            if ($shouldRethrowErrors) {
43
                set_error_handler(static function () {
44
                    self::rethrowJsonError(JSON_ERROR_SYNTAX);
45 17
                }, E_WARNING);
46
            }
47
48 17
            $json = json_encode($value, $options, $depth);
49
50 17
            if ($shouldRethrowErrors) {
51 17
                restore_error_handler();
52 17
                self::rethrowJsonError(json_last_error());
53
            }
54
55 16
            return $json;
56
        }
57
58 20
        private static function shouldRethrowErrors(int $options): bool
59
        {
60 20
            if (!self::hasFlag($options, JSON_THROW_ON_ERROR)) {
61
                return false;
62
            }
63
64 20
            return PHP_VERSION_ID < 70300;
65
        }
66
67 20
        private static function hasFlag(int $flags, int $flag): bool
68
        {
69 20
            return ($flags & $flag) === $flag;
70
        }
71
72
        /**
73
         * Encodes the given value into a JSON string HTML-escaping entities so it is safe to be embedded in HTML code.
74
         *
75
         * Note that data encoded as JSON must be UTF-8 encoded according to the JSON specification.
76
         * You must ensure strings passed to this method have proper encoding before passing them.
77
         *
78
         * @param mixed $value the data to be encoded
79
         * @return string the encoding result
80
         * @throws \JsonException if there is any encoding error
81
         */
82 6
        public static function htmlEncode($value): string
83
        {
84 6
            return self::encode($value, JSON_UNESCAPED_UNICODE | JSON_HEX_QUOT | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_THROW_ON_ERROR);
85
        }
86
87
        /**
88
         * Decodes the given JSON string into a PHP data structure.
89
         * @param string $json the JSON string to be decoded
90
         * @param bool $asArray whether to return objects in terms of associative arrays.
91
         * @param int $depth the recursion depth.
92
         * @param int $options the decode options.
93
         * @return mixed the PHP data
94
         * @throws \JsonException if there is any decoding error
95
         */
96 5
        public static function decode(string $json, bool $asArray = true, int $depth = 512, int $options = JSON_THROW_ON_ERROR)
97
        {
98 5
            if ($json === '') {
99 1
                return null;
100
            }
101 4
            $decode = json_decode($json, $asArray, $depth, $options);
102
103 4
            if (self::shouldRethrowErrors($options)) {
104 4
                self::rethrowJsonError(json_last_error());
105
            }
106 2
            return $decode;
107
        }
108
109
        /**
110
         * Handles [[encode()]] and [[decode()]] errors by throwing exceptions with the respective error message.
111
         *
112
         * @param int $lastError error code from [json_last_error()](http://php.net/manual/en/function.json-last-error.php).
113
         * @throws \JsonException if there is any encoding/decoding error.
114
         */
115 20
        private static function rethrowJsonError(int $lastError): void
116
        {
117 20
            if ($lastError === JSON_ERROR_NONE) {
118 18
                return;
119
            }
120 2
            $availableErrors = [];
121 2
            foreach (self::ERRORS as $constant => $message) {
122 2
                if (defined($constant)) {
123 2
                    $availableErrors[constant($constant)] = $message;
124
                }
125
            }
126 2
            if (isset($availableErrors[$lastError])) {
127 2
                throw new \JsonException($availableErrors[$lastError], $lastError);
128
            }
129
            throw new \JsonException('Unknown JSON encoding/decoding error.');
130
        }
131
132
        /**
133
         * Pre-processes the data before sending it to `json_encode()`.
134
         * @param mixed $data the data to be processed
135
         * @return mixed the processed data
136
         */
137 17
        private static function processData($data)
138
        {
139 17
            if (is_object($data)) {
140 11
                if ($data instanceof \JsonSerializable) {
141 5
                    return self::processData($data->jsonSerialize());
142
                }
143
144 8
                if ($data instanceof \SimpleXMLElement) {
145 1
                    $data = (array)$data;
146
                } else {
147 7
                    $result = [];
148 7
                    foreach ($data as $name => $value) {
149 3
                        $result[$name] = $value;
150
                    }
151 7
                    $data = $result;
152
                }
153 8
                if ($data === []) {
154 4
                    return new \stdClass();
155
                }
156
            }
157 14
            if (is_array($data)) {
158 11
                foreach ($data as $key => $value) {
159 9
                    if (is_array($value) || is_object($value)) {
160 2
                        $data[$key] = self::processData($value);
161
                    }
162
                }
163
            }
164 14
            return $data;
165
        }
166
    }
167
}
168
169
/**
170
 * PHP 7.3 polyfill
171
 */
172
namespace {
173 1
    if (!defined('JSON_THROW_ON_ERROR')) {
174 1
        define('JSON_THROW_ON_ERROR', 4194304);
175
    }
176
177 1
    if (!class_exists('JsonException')) {
178
        class JsonException extends Exception
179
        {
180
181
        }
182
    }
183
}
184