Completed
Pull Request — master (#44)
by Nate
04:05 queued 01:55
created

ArrayTypeAdapter::read()   C

Complexity

Conditions 17
Paths 16

Size

Total Lines 85
Code Lines 49

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 17
eloc 49
nc 16
nop 1
dl 0
loc 85
rs 5.2166
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/*
3
 * Copyright (c) Nate Brunette.
4
 * Distributed under the MIT License (http://opensource.org/licenses/MIT)
5
 */
6
7
declare(strict_types=1);
8
9
namespace Tebru\Gson;
10
11
use LogicException;
12
use stdClass;
13
use Tebru\Gson\Exception\JsonSyntaxException;
14
use Tebru\Gson\Internal\TypeAdapterProvider;
15
use Tebru\PhpType\TypeToken;
16
17
/**
18
 * Class ArrayTypeAdapter
19
 *
20
 * @author Nate Brunette <[email protected]>
21
 */
22
class ArrayTypeAdapter extends TypeAdapter
23
{
24
    /**
25
     * @var TypeAdapterProvider
26
     */
27
    private $typeAdapterProvider;
28
29
    /**
30
     * @var TypeToken
31
     */
32
    private $keyType;
33
34
    /**
35
     * @var TypeAdapter
36
     */
37
    private $valueTypeAdapter;
38
39
    /**
40
     * @var int
41
     */
42
    private $numberOfGenerics;
43
44
    /**
45
     * Constructor
46
     *
47
     * @param TypeAdapterProvider $typeAdapterProvider
48
     * @param TypeToken $keyType
49
     * @param TypeAdapter $valueTypeAdapter
50
     * @param int $numberOfGenerics
51
     */
52
    public function __construct(
53
        TypeAdapterProvider $typeAdapterProvider,
54
        TypeToken $keyType,
55
        TypeAdapter $valueTypeAdapter,
56
        int $numberOfGenerics
57
    ) {
58
        $this->typeAdapterProvider = $typeAdapterProvider;
59
        $this->keyType = $keyType;
60
        $this->valueTypeAdapter = $valueTypeAdapter;
61
        $this->numberOfGenerics = $numberOfGenerics;
62
    }
63
64
    /**
65
     * Read the next value, convert it to its type and return it
66
     *
67
     * @param JsonReadable $reader
68
     * @return array|null
69
     * @throws \LogicException
70
     * @throws \Tebru\Gson\Exception\JsonSyntaxException If trying to read from non object/array
71
     */
72
    public function read(JsonReadable $reader): ?array
73
    {
74
        $token = $reader->peek();
75
        if ($token === JsonToken::NULL) {
76
            $reader->nextNull();
77
            return null;
78
        }
79
80
        $array = [];
81
82
        if ($this->numberOfGenerics > 2) {
83
            throw new LogicException(\sprintf('Array may not have more than 2 generic types at "%s"', $reader->getPath()));
84
        }
85
86
        switch ($token) {
87
            case JsonToken::BEGIN_OBJECT:
88
                $reader->beginObject();
89
90
                while ($reader->hasNext()) {
91
                    $name = $reader->nextName();
92
93
                    switch ($this->numberOfGenerics) {
94
                        // no generics specified
95
                        case 0:
96
                            // By now we know that we're deserializing a json object to an array.
97
                            // If there is a nested object, continue deserializing to an array,
98
                            // otherwise guess the type using the wildcard
99
                            $type = $reader->peek() === JsonToken::BEGIN_OBJECT
100
                                ? TypeToken::create(TypeToken::HASH)
101
                                : TypeToken::create(TypeToken::WILDCARD);
102
103
                            $adapter = $this->typeAdapterProvider->getAdapter($type);
104
                            $array[$name] = $adapter->read($reader);
105
                            break;
106
                        // generic for value specified
107
                        case 1:
108
                            $array[$name] = $this->valueTypeAdapter->read($reader);
109
                            break;
110
                        // generic for key and value specified
111
                        case 2:
112
                            if (!$this->keyType->isString() && !$this->keyType->isInteger()) {
113
                                throw new LogicException(\sprintf('Array keys must be strings or integers at "%s"', $reader->getPath()));
114
                            }
115
116
                            if ($this->keyType->isInteger()) {
117
                                if (!\ctype_digit($name)) {
118
                                    throw new JsonSyntaxException(\sprintf('Expected integer, but found string for key at "%s"', $reader->getPath()));
119
                                }
120
121
                                $name = (int)$name;
122
                            }
123
124
                            $array[$name] = $this->valueTypeAdapter->read($reader);
125
126
                            break;
127
                    }
128
                }
129
130
                $reader->endObject();
131
132
                break;
133
            case JsonToken::BEGIN_ARRAY:
134
                $reader->beginArray();
135
136
                while ($reader->hasNext()) {
137
                    switch ($this->numberOfGenerics) {
138
                        // no generics specified
139
                        case 0:
140
                        case 1:
141
                            $array[] = $this->valueTypeAdapter->read($reader);
142
143
                            break;
144
                        default:
145
                            throw new LogicException(\sprintf('An array may only specify a generic type for the value at "%s"', $reader->getPath()));
146
                    }
147
                }
148
149
                $reader->endArray();
150
151
                break;
152
            default:
153
                throw new JsonSyntaxException(\sprintf('Could not parse json, expected array or object but found "%s" at "%s"', $token, $reader->getPath()));
154
        }
155
156
        return $array;
157
    }
158
159
    /**
160
     * Write the value to the writer for the type
161
     *
162
     * @param JsonWritable $writer
163
     * @param array|stdClass|null $value
164
     * @return void
165
     * @throws \LogicException
166
     */
167
    public function write(JsonWritable $writer, $value): void
168
    {
169
        if (null === $value) {
170
            $writer->writeNull();
171
172
            return;
173
        }
174
175
        if ($this->numberOfGenerics > 2) {
176
            throw new LogicException(\sprintf('Array may not have more than 2 generic types at "%s"', $writer->getPath()));
177
        }
178
179
        $arrayIsObject = $this->isArrayObject($value, $this->numberOfGenerics);
180
181
        if ($arrayIsObject) {
182
            $writer->beginObject();
183
        } else {
184
            $writer->beginArray();
185
        }
186
187
        foreach ($value as $key => $item) {
188
            switch ($this->numberOfGenerics) {
189
                // no generics specified
190
                case 0:
191
                    if ($arrayIsObject) {
192
                        $writer->name((string)$key);
193
                    }
194
195
                    $adapter = $this->typeAdapterProvider->getAdapter(TypeToken::createFromVariable($item));
196
                    $adapter->write($writer, $item);
197
198
                    break;
199
                // generic for value specified
200
                case 1:
201
                    if ($arrayIsObject) {
202
                        $writer->name((string)$key);
203
                    }
204
205
                    $this->valueTypeAdapter->write($writer, $item);
206
207
                    break;
208
                // generic for key and value specified
209
                case 2:
210
                    $writer->name($key);
211
                    $this->valueTypeAdapter->write($writer, $item);
212
213
                    break;
214
            }
215
        }
216
217
        if ($arrayIsObject) {
218
            $writer->endObject();
219
        } else {
220
            $writer->endArray();
221
        }
222
    }
223
224
    /**
225
     * Returns true if the array is acting like an object
226
     * @param array|stdClass $array
227
     * @param int $numberOfGenerics
228
     * @return bool
229
     */
230
    private function isArrayObject($array, int $numberOfGenerics): bool
231
    {
232
        if (2 === $numberOfGenerics) {
233
            return true;
234
        }
235
236
        return \is_string(\key($array));
0 ignored issues
show
Bug introduced by
It seems like $array can also be of type stdClass; however, parameter $array of key() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

236
        return \is_string(\key(/** @scrutinizer ignore-type */ $array));
Loading history...
237
    }
238
}
239