Passed
Pull Request — master (#920)
by Asmir
03:17
created

JsonDeserializationVisitor::getResult()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * Copyright 2016 Johannes M. Schmitt <[email protected]>
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 *     http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
21
namespace JMS\Serializer;
22
23
use JMS\Serializer\Accessor\AccessorStrategyInterface;
24
use JMS\Serializer\Exception\LogicException;
25
use JMS\Serializer\Exception\RuntimeException;
26
use JMS\Serializer\Metadata\ClassMetadata;
27
use JMS\Serializer\Metadata\PropertyMetadata;
28
29
class JsonDeserializationVisitor extends AbstractVisitor implements DeserializationVisitorInterface
30
{
31
    private $options = 0;
32
    private $depth = 512;
33
34
    private $objectStack;
35
    private $currentObject;
36
37 59
    public function __construct(
38
        GraphNavigatorInterface $navigator,
39
        AccessorStrategyInterface $accessorStrategy,
40
        DeserializationContext $context,
41
        int $options = 0, int $depth = 512)
42
    {
43 59
        parent::__construct($navigator, $accessorStrategy, $context);
44 59
        $this->objectStack = new \SplStack;
45 59
        $this->options = $options;
46 59
        $this->depth = $depth;
47 59
    }
48
49 1
    public function visitNull($data, array $type): void
50
    {
51
52 1
    }
53
54 23
    public function visitString($data, array $type): string
55
    {
56 23
        return (string)$data;
57
    }
58
59 5
    public function visitBoolean($data, array $type): bool
60
    {
61 5
        return (bool)$data;
62
    }
63
64 8
    public function visitInteger($data, array $type): int
65
    {
66 8
        return (int)$data;
67
    }
68
69 13
    public function visitDouble($data, array $type): float
70
    {
71 13
        return (double)$data;
72
    }
73
74 19
    public function visitArray($data, array $type): array
75
    {
76 19
        if (!\is_array($data)) {
77
            throw new RuntimeException(sprintf('Expected array, but got %s: %s', \gettype($data), json_encode($data)));
78
        }
79
80
        // If no further parameters were given, keys/values are just passed as is.
81 19
        if (!$type['params']) {
82 4
            return $data;
83
        }
84
85 15
        switch (\count($type['params'])) {
86 15
            case 1: // Array is a list.
87 13
                $listType = $type['params'][0];
88
89 13
                $result = array();
90
91 13
                foreach ($data as $v) {
92 12
                    $result[] = $this->navigator->accept($v, $listType, $this->context);
93
                }
94
95 13
                return $result;
96
97 5
            case 2: // Array is a map.
98 5
                list($keyType, $entryType) = $type['params'];
99
100 5
                $result = array();
101
102 5
                foreach ($data as $k => $v) {
103 5
                    $result[$this->navigator->accept($k, $keyType, $this->context)] = $this->navigator->accept($v, $entryType, $this->context);
104
                }
105
106 5
                return $result;
107
108
            default:
109
                throw new RuntimeException(sprintf('Array type cannot have more than 2 parameters, but got %s.', json_encode($type['params'])));
110
        }
111
    }
112
113 6
    public function visitDiscriminatorMapProperty($data, ClassMetadata $metadata): string
114
    {
115 6
        if (isset($data[$metadata->discriminatorFieldName])) {
116 5
            return (string)$data[$metadata->discriminatorFieldName];
117
        }
118
119 1
        throw new LogicException(sprintf(
120 1
            'The discriminator field name "%s" for base-class "%s" was not found in input data.',
121 1
            $metadata->discriminatorFieldName,
122 1
            $metadata->name
123
        ));
124
    }
125
126 30
    public function startVisitingObject(ClassMetadata $metadata, object $object, array $type): void
127
    {
128 30
        $this->setCurrentObject($object);
129 30
    }
130
131 30
    public function visitProperty(PropertyMetadata $metadata, $data): void
132
    {
133 30
        $name = $metadata->serializedName;
134
135 30
        if (null === $data) {
136
            return;
137
        }
138
139 30
        if (!\is_array($data)) {
140 1
            throw new RuntimeException(sprintf('Invalid data "%s"(%s), expected "%s".', $data, $metadata->type['name'], $metadata->reflection->class));
141
        }
142
143 30
        if (!array_key_exists($name, $data)) {
144 3
            return;
145
        }
146
147 28
        if (!$metadata->type) {
148
            throw new RuntimeException(sprintf('You must define a type for %s::$%s.', $metadata->reflection->class, $metadata->name));
149
        }
150
151 28
        $v = $data[$name] !== null ? $this->navigator->accept($data[$name], $metadata->type, $this->context) : null;
152
153 28
        $this->accessor->setValue($this->currentObject, $v, $metadata);
154
155 28
    }
156
157 29
    public function endVisitingObject(ClassMetadata $metadata, $data, array $type): object
158
    {
159 29
        $obj = $this->currentObject;
160 29
        $this->revertCurrentObject();
161
162 29
        return $obj;
163
    }
164
165 55
    public function getResult($data)
166
    {
167 55
        return $data;
168
    }
169
170 30
    public function setCurrentObject($object)
171
    {
172 30
        $this->objectStack->push($this->currentObject);
173 30
        $this->currentObject = $object;
174 30
    }
175
176
    public function getCurrentObject()
177
    {
178
        return $this->currentObject;
179
    }
180
181 29
    public function revertCurrentObject()
182
    {
183 29
        return $this->currentObject = $this->objectStack->pop();
184
    }
185
186 57
    public function prepare($str)
187
    {
188 57
        $decoded = json_decode($str, true, $this->depth, $this->options);
189
190 57
        switch (json_last_error()) {
191 57
            case JSON_ERROR_NONE:
192 57
                return $decoded;
193
194
            case JSON_ERROR_DEPTH:
195
                throw new RuntimeException('Could not decode JSON, maximum stack depth exceeded.');
196
197
            case JSON_ERROR_STATE_MISMATCH:
198
                throw new RuntimeException('Could not decode JSON, underflow or the nodes mismatch.');
199
200
            case JSON_ERROR_CTRL_CHAR:
201
                throw new RuntimeException('Could not decode JSON, unexpected control character found.');
202
203
            case JSON_ERROR_SYNTAX:
204
                throw new RuntimeException('Could not decode JSON, syntax error - malformed JSON.');
205
206
            case JSON_ERROR_UTF8:
207
                throw new RuntimeException('Could not decode JSON, malformed UTF-8 characters (incorrectly encoded?)');
208
209
            default:
210
                throw new RuntimeException('Could not decode JSON.');
211
        }
212
    }
213
}
214