Skip to content

Commit 0913545

Browse files
committed
Initial working version
1 parent 532bd66 commit 0913545

File tree

6 files changed

+259
-46
lines changed

6 files changed

+259
-46
lines changed

.gitattributes

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Make sure line endings are normalized to LF for Windows
2+
* text eol=lf
3+
4+
# Remove developer files from exports
5+
tests export-ignore
6+
.gitattributes export-ignore
7+
.gitignore export-ignore
8+
.php_cs export-ignore
9+
.travis.yml export-ignore
10+
phpunit.xml export-ignore

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
coverage/
2+
vendor/
3+
composer.lock

composer.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,11 @@
2525
"psr-4": {
2626
"Violet\\StreamingJsonEncoder\\": "src/"
2727
}
28+
},
29+
"autoload-dev": {
30+
"psr-4": {
31+
"Violet\\StreamingJsonEncoder\\": "tests/tests/",
32+
"Violet\\StreamingJsonEncoder\\Test\\": "tests/classes/"
33+
}
2834
}
2935
}

src/StreamingJsonEncoder.php

Lines changed: 80 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,32 @@ class StreamingJsonEncoder
3838
/** @var int */
3939
private $column;
4040

41+
/** @var int */
42+
private $traversedBytes;
43+
4144
public function __construct()
4245
{
4346
$this->indent = ' ';
4447
$this->newLine = false;
4548
}
4649

50+
public function getErrors()
51+
{
52+
return $this->encodingErrors;
53+
}
54+
55+
private function pushError($message)
56+
{
57+
$errorMessage = sprintf('Line %d, column %d: %s', $this->line, $this->column, $message);
58+
$this->encodingErrors[] = $errorMessage;
59+
60+
if ($this->options & JSON_PARTIAL_OUTPUT_ON_ERROR) {
61+
return;
62+
}
63+
64+
throw new EncodingException($errorMessage);
65+
}
66+
4767
public function encode($value, $options = 0)
4868
{
4969
$this->encodingErrors = [];
@@ -52,72 +72,87 @@ public function encode($value, $options = 0)
5272
$this->line = 1;
5373
$this->column = 1;
5474

75+
return $this->processValue($value);
76+
}
77+
78+
private function resolveValue($value)
79+
{
5580
while ($value instanceof \JsonSerializable) {
5681
$value = $value->jsonSerialize();
5782
}
5883

84+
return $value;
85+
}
86+
87+
private function processValue($value)
88+
{
89+
$value = $this->resolveValue($value);
90+
5991
if (is_array($value) || is_object($value)) {
60-
$this->traverse($value);
61-
} else {
62-
$this->output($this->encodeValue($value));
92+
if (empty($this->generatorStack)) {
93+
return $this->traverse($value);
94+
}
95+
96+
return $this->pushIterable($value);
6397
}
98+
99+
return $this->output($this->encodeValue($value));
64100
}
65101

66102
private function traverse($traversable)
67103
{
104+
$this->traversedBytes = 0;
68105
$this->generatorStack = [];
69106
$this->typeStack = [];
70107
$this->first = true;
71108

72-
$this->pushIterable($traversable);
109+
$bytes = $this->pushIterable($traversable);
73110
$keySeparator = $this->options & JSON_PRETTY_PRINT ? ': ' : ':';
74-
$null = json_encode(null);
75111

76112
foreach ($this->traverseStack() as $key => $value) {
113+
if (!is_int($key) && !is_string($key)) {
114+
$this->pushError('Only string or integer keys are supported');
115+
continue;
116+
}
117+
77118
if (!$this->first) {
78-
$this->outputLine(',');
119+
$bytes += $this->outputLine(',');
79120
}
80121

81122
$this->first = false;
82123

83124
if (end($this->typeStack)) {
84-
$encoded = $this->encodeValue((string) $key);
85-
86-
if ($encoded === $null) {
87-
continue;
88-
}
89-
90-
$this->output($encoded . $keySeparator);
91-
}
92-
93-
while ($value instanceof \JsonSerializable) {
94-
$value = $value->jsonSerialize();
125+
$bytes += $this->output($this->encodeValue((string) $key) . $keySeparator);
95126
}
96127

97-
if (is_array($value) || is_object($value)) {
98-
$this->pushIterable($value);
99-
} else {
100-
$this->output($this->encodeValue($value));
101-
}
128+
$bytes += $this->processValue($value);
102129
}
130+
131+
return $bytes + $this->traversedBytes;
103132
}
104133

105134
private function pushIterable($iterable)
106135
{
107136
$this->generatorStack[] = $this->iterate($iterable);
108137
$this->first = true;
109138

139+
$isObject = $this->isObject($iterable);
140+
$bytes = $this->outputLine($isObject ? '{' : '[');
141+
$this->typeStack[] = $isObject;
142+
143+
return $bytes;
144+
}
145+
146+
private function isObject($iterable)
147+
{
110148
if ($this->options & JSON_FORCE_OBJECT) {
111-
$object = true;
149+
return true;
112150
} elseif (is_array($iterable)) {
113-
$object = array_keys($iterable) !== range(0, count($iterable) - 1);
114-
} else {
115-
$generator = end($this->generatorStack);
116-
$object = $generator->valid() && $generator->key() === 0;
151+
return $iterable !== [] && array_keys($iterable) !== range(0, count($iterable) - 1);
117152
}
118153

119-
$this->outputLine($object ? '{' : '[');
120-
$this->typeStack[] = $object;
154+
$generator = end($this->generatorStack);
155+
return $generator->valid() && $generator->key() !== 0;
121156
}
122157

123158
private function popIterable()
@@ -129,7 +164,7 @@ private function popIterable()
129164
$this->first = false;
130165
array_pop($this->generatorStack);
131166
$object = array_pop($this->typeStack);
132-
$this->output($object ? '}' : ']');
167+
return $this->output($object ? '}' : ']');
133168
}
134169

135170
public function traverseStack()
@@ -141,7 +176,7 @@ public function traverseStack()
141176
yield $active->key() => $active->current();
142177
$active->next();
143178
} else {
144-
$this->popIterable();
179+
$this->traversedBytes += $this->popIterable();
145180
}
146181
}
147182
}
@@ -155,42 +190,44 @@ public function iterate($iterable)
155190

156191
private function output($string)
157192
{
193+
$bytes = 0;
194+
158195
if ($this->newLine && $this->options & JSON_PRETTY_PRINT) {
159-
$this->write("\n");
196+
$bytes += $this->write("\n");
160197
$this->line++;
161198
$this->column = 1;
162-
$this->write(str_repeat($this->indent, count($this->typeStack)));
199+
$bytes += $this->write(str_repeat($this->indent, count($this->typeStack)));
163200
}
164201

165202
$this->newLine = false;
166-
$this->write($string);
203+
$bytes += $this->write($string);
204+
205+
return $bytes;
167206
}
168207

169208
private function outputLine($string)
170209
{
171-
$this->output($string);
210+
$bytes = $this->output($string);
172211
$this->newLine = true;
212+
213+
return $bytes;
173214
}
174215

175216
private function write($string)
176217
{
177218
echo $string;
219+
178220
$this->column += strlen($string);
221+
return strlen($string);
179222
}
180223

181224
private function encodeValue($value)
182225
{
183226
$encoded = json_encode($value, $this->options);
184227

185228
if (json_last_error() !== JSON_ERROR_NONE) {
186-
$this->encodingErrors[] =
187-
sprintf('Line %d, column %d: %s', $this->line, $this->column, json_last_error_msg());
188-
189-
if ($this->options & JSON_PARTIAL_OUTPUT_ON_ERROR) {
190-
return $encoded === false ? json_encode(null) : $encoded;
191-
}
192-
193-
throw new EncodingException(end($this->encodingErrors));
229+
$this->pushError(json_last_error_msg());
230+
return $encoded === false ? json_encode(null) : $encoded;
194231
}
195232

196233
return $encoded;

tests/classes/SerializableData.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace Violet\StreamingJsonEncoder\Test;
4+
5+
/**
6+
* SerializableData.
7+
*
8+
* @author Riikka Kalliomäki <[email protected]>
9+
* @copyright Copyright (c) 2016, Riikka Kalliomäki
10+
* @license http://opensource.org/licenses/mit-license.php MIT License
11+
*/
12+
class SerializableData implements \JsonSerializable
13+
{
14+
private $data;
15+
16+
public function __construct($data)
17+
{
18+
$this->data = $data;
19+
}
20+
21+
public function jsonSerialize()
22+
{
23+
return $this->data;
24+
}
25+
}

0 commit comments

Comments
 (0)