1<?php 2 3declare(strict_types=1); 4 5namespace JMS\Serializer; 6 7use JMS\Serializer\Exception\NotAcceptableException; 8use JMS\Serializer\Exception\RuntimeException; 9use JMS\Serializer\Metadata\ClassMetadata; 10use JMS\Serializer\Metadata\PropertyMetadata; 11use JMS\Serializer\Visitor\SerializationVisitorInterface; 12 13final class JsonSerializationVisitor extends AbstractVisitor implements SerializationVisitorInterface 14{ 15 /** 16 * @var int 17 */ 18 private $options = JSON_PRESERVE_ZERO_FRACTION; 19 20 /** 21 * @var array 22 */ 23 private $dataStack = []; 24 /** 25 * @var \ArrayObject 26 */ 27 private $data; 28 29 public function __construct( 30 int $options = JSON_PRESERVE_ZERO_FRACTION 31 ) { 32 $this->dataStack = []; 33 $this->options = $options; 34 } 35 36 /** 37 * {@inheritdoc} 38 */ 39 public function visitNull($data, array $type) 40 { 41 return null; 42 } 43 44 /** 45 * {@inheritdoc} 46 */ 47 public function visitString(string $data, array $type) 48 { 49 return $data; 50 } 51 52 /** 53 * {@inheritdoc} 54 */ 55 public function visitBoolean(bool $data, array $type) 56 { 57 return $data; 58 } 59 60 /** 61 * {@inheritdoc} 62 */ 63 public function visitInteger(int $data, array $type) 64 { 65 return $data; 66 } 67 68 /** 69 * {@inheritdoc} 70 */ 71 public function visitDouble(float $data, array $type) 72 { 73 return $data; 74 } 75 76 /** 77 * @param array $data 78 * @param array $type 79 * 80 * @return array|\ArrayObject 81 */ 82 public function visitArray(array $data, array $type) 83 { 84 \array_push($this->dataStack, $data); 85 86 $rs = isset($type['params'][1]) ? new \ArrayObject() : []; 87 88 $isList = isset($type['params'][0]) && !isset($type['params'][1]); 89 90 $elType = $this->getElementType($type); 91 foreach ($data as $k => $v) { 92 try { 93 $v = $this->navigator->accept($v, $elType); 94 } catch (NotAcceptableException $e) { 95 continue; 96 } 97 98 if ($isList) { 99 $rs[] = $v; 100 } else { 101 $rs[$k] = $v; 102 } 103 } 104 105 \array_pop($this->dataStack); 106 return $rs; 107 } 108 109 public function startVisitingObject(ClassMetadata $metadata, object $data, array $type): void 110 { 111 \array_push($this->dataStack, $this->data); 112 $this->data = true === $metadata->isMap ? new \ArrayObject() : []; 113 } 114 115 /** 116 * @return array|\ArrayObject 117 */ 118 public function endVisitingObject(ClassMetadata $metadata, object $data, array $type) 119 { 120 $rs = $this->data; 121 $this->data = \array_pop($this->dataStack); 122 123 if (true !== $metadata->isList && empty($rs)) { 124 return new \ArrayObject(); 125 } 126 127 return $rs; 128 } 129 130 /** 131 * {@inheritdoc} 132 */ 133 public function visitProperty(PropertyMetadata $metadata, $v): void 134 { 135 try { 136 $v = $this->navigator->accept($v, $metadata->type); 137 } catch (NotAcceptableException $e) { 138 return; 139 } 140 141 if (true === $metadata->skipWhenEmpty && ($v instanceof \ArrayObject || \is_array($v)) && 0 === count($v)) { 142 return; 143 } 144 145 if ($metadata->inline) { 146 if (\is_array($v) || ($v instanceof \ArrayObject)) { 147 // concatenate the two array-like structures 148 // is there anything faster? 149 foreach ($v as $key => $value) { 150 $this->data[$key] = $value; 151 } 152 } 153 } else { 154 $this->data[$metadata->serializedName] = $v; 155 } 156 } 157 158 /** 159 * @deprecated Will be removed in 3.0 160 * 161 * Checks if some data key exists. 162 */ 163 public function hasData(string $key): bool 164 { 165 return isset($this->data[$key]); 166 } 167 168 /** 169 * @deprecated Use visitProperty(new StaticPropertyMetadata(null, 'name', 'value'), null) instead 170 * 171 * Allows you to replace existing data on the current object element. 172 * 173 * @param mixed $value This value must either be a regular scalar, or an array. 174 * It must not contain any objects anymore. 175 */ 176 public function setData(string $key, $value): void 177 { 178 $this->data[$key] = $value; 179 } 180 181 /** 182 * {@inheritdoc} 183 */ 184 public function getResult($data) 185 { 186 $result = @json_encode($data, $this->options); 187 188 switch (json_last_error()) { 189 case JSON_ERROR_NONE: 190 return $result; 191 192 case JSON_ERROR_UTF8: 193 throw new RuntimeException('Your data could not be encoded because it contains invalid UTF8 characters.'); 194 195 default: 196 throw new RuntimeException(sprintf('An error occurred while encoding your data (error code %d).', json_last_error())); 197 } 198 } 199} 200