1
|
|
|
<?php |
2
|
|
|
/* |
3
|
|
|
* Copyright (c) Nate Brunette. |
4
|
|
|
* Distributed under the MIT License (http://opensource.org/licenses/MIT) |
5
|
|
|
*/ |
6
|
|
|
|
7
|
|
|
namespace Tebru\Gson\Internal; |
8
|
|
|
|
9
|
|
|
use ArrayIterator; |
10
|
|
|
use stdClass; |
11
|
|
|
use Tebru\Gson\Exception\MalformedJsonException; |
12
|
|
|
use Tebru\Gson\JsonToken; |
13
|
|
|
|
14
|
|
|
/** |
15
|
|
|
* Class JsonDecodeReader |
16
|
|
|
* |
17
|
|
|
* @author Nate Brunette <[email protected]> |
18
|
|
|
*/ |
19
|
|
|
final class JsonDecodeReader extends JsonReader |
20
|
|
|
{ |
21
|
|
|
/** |
22
|
|
|
* Constructor |
23
|
|
|
* |
24
|
|
|
* @param string $json |
25
|
|
|
* @throws \Tebru\Gson\Exception\MalformedJsonException If the json cannot be decoded |
26
|
|
|
*/ |
27
|
83 |
|
public function __construct(string $json) |
28
|
|
|
{ |
29
|
83 |
|
$this->push(json_decode($json)); |
30
|
|
|
|
31
|
83 |
|
if (json_last_error() !== JSON_ERROR_NONE) { |
32
|
1 |
|
throw new MalformedJsonException(sprintf('Could not decode json, the error message was: "%s"', json_last_error_msg())); |
33
|
|
|
} |
34
|
82 |
|
} |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* Consumes the next token and asserts it's the beginning of a new array |
38
|
|
|
* |
39
|
|
|
* @return void |
40
|
|
|
* @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not BEGIN_ARRAY |
41
|
|
|
*/ |
42
|
24 |
View Code Duplication |
public function beginArray(): void |
|
|
|
|
43
|
|
|
{ |
44
|
24 |
|
$this->expect(JsonToken::BEGIN_ARRAY); |
45
|
|
|
|
46
|
23 |
|
$array = $this->pop(); |
47
|
23 |
|
$this->push(new ArrayIterator($array), ArrayIterator::class); |
48
|
23 |
|
$this->pathIndices[$this->stackSize - 1] = 0; |
49
|
23 |
|
} |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* Consumes the next token and asserts it's the beginning of a new object |
53
|
|
|
* |
54
|
|
|
* @return void |
55
|
|
|
* @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not BEGIN_OBJECT |
56
|
|
|
*/ |
57
|
24 |
|
public function beginObject(): void |
58
|
|
|
{ |
59
|
24 |
|
$this->expect(JsonToken::BEGIN_OBJECT); |
60
|
|
|
|
61
|
23 |
|
$this->push(new StdClassIterator($this->pop()), StdClassIterator::class); |
62
|
23 |
|
} |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* Consumes the value of the next token, asserts it's a boolean and returns it |
66
|
|
|
* |
67
|
|
|
* @return bool |
68
|
|
|
* @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not BOOLEAN |
69
|
|
|
*/ |
70
|
6 |
|
public function nextBoolean(): bool |
71
|
|
|
{ |
72
|
6 |
|
$this->expect(JsonToken::BOOLEAN); |
73
|
|
|
|
74
|
6 |
|
$result = (bool)$this->pop(); |
75
|
|
|
|
76
|
6 |
|
$this->incrementPathIndex(); |
77
|
|
|
|
78
|
6 |
|
return $result; |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* Consumes the value of the next token, asserts it's a double and returns it |
83
|
|
|
* |
84
|
|
|
* @return double |
85
|
|
|
* @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not NUMBER |
86
|
|
|
*/ |
87
|
3 |
|
public function nextDouble(): float |
88
|
|
|
{ |
89
|
3 |
|
$this->expect(JsonToken::NUMBER); |
90
|
|
|
|
91
|
2 |
|
$result = (float)$this->pop(); |
92
|
|
|
|
93
|
2 |
|
$this->incrementPathIndex(); |
94
|
|
|
|
95
|
2 |
|
return $result; |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* Consumes the value of the next token, asserts it's an int and returns it |
100
|
|
|
* |
101
|
|
|
* @return int |
102
|
|
|
* @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not NUMBER |
103
|
|
|
*/ |
104
|
15 |
|
public function nextInteger(): int |
105
|
|
|
{ |
106
|
15 |
|
$this->expect(JsonToken::NUMBER); |
107
|
|
|
|
108
|
14 |
|
$result = (int)$this->pop(); |
109
|
|
|
|
110
|
14 |
|
$this->incrementPathIndex(); |
111
|
|
|
|
112
|
14 |
|
return $result; |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* Consumes the value of the next token, asserts it's a string and returns it |
117
|
|
|
* |
118
|
|
|
* @return string |
119
|
|
|
* @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not NAME or STRING |
120
|
|
|
*/ |
121
|
13 |
View Code Duplication |
public function nextString(): string |
|
|
|
|
122
|
|
|
{ |
123
|
13 |
|
$peek = $this->peek(); |
124
|
13 |
|
if ($peek === JsonToken::NAME) { |
125
|
1 |
|
return $this->nextName(); |
126
|
|
|
} |
127
|
|
|
|
128
|
12 |
|
$this->expect(JsonToken::STRING); |
129
|
|
|
|
130
|
11 |
|
$result = (string)$this->pop(); |
131
|
|
|
|
132
|
11 |
|
$this->incrementPathIndex(); |
133
|
|
|
|
134
|
11 |
|
return $result; |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* Returns an enum representing the type of the next token without consuming it |
139
|
|
|
* |
140
|
|
|
* @return string |
141
|
|
|
*/ |
142
|
81 |
|
public function peek(): string |
143
|
|
|
{ |
144
|
81 |
|
if (null !== $this->currentToken) { |
145
|
22 |
|
return $this->currentToken; |
146
|
|
|
} |
147
|
|
|
|
148
|
81 |
|
if (0 === $this->stackSize) { |
149
|
1 |
|
$this->currentToken = JsonToken::END_DOCUMENT; |
150
|
|
|
|
151
|
1 |
|
return $this->currentToken; |
152
|
|
|
} |
153
|
|
|
|
154
|
81 |
|
$token = null; |
155
|
81 |
|
$element = $this->stack[$this->stackSize - 1]; |
156
|
|
|
|
157
|
81 |
|
switch ($this->stackTypes[$this->stackSize - 1]) { |
158
|
81 |
|
case 'array': |
159
|
25 |
|
$token = JsonToken::BEGIN_ARRAY; |
160
|
25 |
|
break; |
161
|
77 |
|
case 'string': |
162
|
18 |
|
$token = JsonToken::STRING; |
163
|
18 |
|
break; |
164
|
65 |
|
case 'double': |
165
|
1 |
|
$token = JsonToken::NUMBER; |
166
|
1 |
|
break; |
167
|
64 |
|
case 'integer': |
168
|
30 |
|
$token = JsonToken::NUMBER; |
169
|
30 |
|
break; |
170
|
50 |
|
case 'boolean': |
171
|
8 |
|
return JsonToken::BOOLEAN; |
172
|
46 |
|
case 'NULL': |
173
|
2 |
|
$token = JsonToken::NULL; |
174
|
2 |
|
break; |
175
|
44 |
|
case StdClassIterator::class: |
176
|
|
|
/** @var StdClassIterator $element */ |
177
|
21 |
|
$token = $element->valid() ? JsonToken::NAME : JsonToken::END_OBJECT; |
178
|
21 |
|
break; |
179
|
44 |
View Code Duplication |
case ArrayIterator::class: |
|
|
|
|
180
|
|
|
/** @var ArrayIterator $element */ |
181
|
21 |
|
if ($element->valid()) { |
182
|
17 |
|
$this->push($element->current()); |
183
|
17 |
|
$element->next(); |
184
|
|
|
|
185
|
17 |
|
$token = $this->peek(); |
186
|
|
|
} else { |
187
|
9 |
|
$token = JsonToken::END_ARRAY; |
188
|
|
|
} |
189
|
21 |
|
break; |
190
|
27 |
|
case 'object': |
191
|
27 |
|
switch (get_class($element)) { |
192
|
27 |
|
case stdClass::class: |
193
|
27 |
|
$token = JsonToken::BEGIN_OBJECT; |
194
|
27 |
|
break; |
195
|
|
|
} |
196
|
27 |
|
break; |
197
|
|
|
} |
198
|
|
|
|
199
|
77 |
|
$this->currentToken = $token; |
200
|
|
|
|
201
|
77 |
|
return $this->currentToken; |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
/** |
205
|
|
|
* Get the current read path in json xpath format |
206
|
|
|
* |
207
|
|
|
* @return string |
208
|
|
|
*/ |
209
|
23 |
View Code Duplication |
public function getPath(): string |
|
|
|
|
210
|
|
|
{ |
211
|
23 |
|
$result = '$'; |
212
|
23 |
|
foreach ($this->stack as $index => $item) { |
213
|
21 |
|
if ($item instanceof ArrayIterator && isset($this->pathIndices[$index])) { |
214
|
9 |
|
$result .= '['.$this->pathIndices[$index].']'; |
215
|
|
|
} |
216
|
|
|
|
217
|
21 |
|
if ($item instanceof StdClassIterator && isset($this->pathNames[$index])) { |
218
|
21 |
|
$result .= '.'.$this->pathNames[$index]; |
219
|
|
|
} |
220
|
|
|
} |
221
|
|
|
|
222
|
23 |
|
return $result; |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
/** |
226
|
|
|
* Push an element onto the stack |
227
|
|
|
* |
228
|
|
|
* @param mixed $element |
229
|
|
|
* @param string $type |
|
|
|
|
230
|
|
|
*/ |
231
|
83 |
View Code Duplication |
protected function push($element, $type = null): void |
|
|
|
|
232
|
|
|
{ |
233
|
83 |
|
if (null === $type) { |
234
|
83 |
|
$type = gettype($element); |
235
|
|
|
} |
236
|
|
|
|
237
|
83 |
|
$this->stack[$this->stackSize] = $element; |
238
|
83 |
|
$this->stackTypes[$this->stackSize] = $type; |
239
|
83 |
|
$this->stackSize++; |
240
|
83 |
|
$this->currentToken = null; |
241
|
83 |
|
} |
242
|
|
|
} |
243
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.