@@ -38,12 +38,32 @@ class StreamingJsonEncoder
38
38
/** @var int */
39
39
private $ column ;
40
40
41
+ /** @var int */
42
+ private $ traversedBytes ;
43
+
41
44
public function __construct ()
42
45
{
43
46
$ this ->indent = ' ' ;
44
47
$ this ->newLine = false ;
45
48
}
46
49
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
+
47
67
public function encode ($ value , $ options = 0 )
48
68
{
49
69
$ this ->encodingErrors = [];
@@ -52,72 +72,87 @@ public function encode($value, $options = 0)
52
72
$ this ->line = 1 ;
53
73
$ this ->column = 1 ;
54
74
75
+ return $ this ->processValue ($ value );
76
+ }
77
+
78
+ private function resolveValue ($ value )
79
+ {
55
80
while ($ value instanceof \JsonSerializable) {
56
81
$ value = $ value ->jsonSerialize ();
57
82
}
58
83
84
+ return $ value ;
85
+ }
86
+
87
+ private function processValue ($ value )
88
+ {
89
+ $ value = $ this ->resolveValue ($ value );
90
+
59
91
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 );
63
97
}
98
+
99
+ return $ this ->output ($ this ->encodeValue ($ value ));
64
100
}
65
101
66
102
private function traverse ($ traversable )
67
103
{
104
+ $ this ->traversedBytes = 0 ;
68
105
$ this ->generatorStack = [];
69
106
$ this ->typeStack = [];
70
107
$ this ->first = true ;
71
108
72
- $ this ->pushIterable ($ traversable );
109
+ $ bytes = $ this ->pushIterable ($ traversable );
73
110
$ keySeparator = $ this ->options & JSON_PRETTY_PRINT ? ': ' : ': ' ;
74
- $ null = json_encode (null );
75
111
76
112
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
+
77
118
if (!$ this ->first ) {
78
- $ this ->outputLine (', ' );
119
+ $ bytes += $ this ->outputLine (', ' );
79
120
}
80
121
81
122
$ this ->first = false ;
82
123
83
124
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 );
95
126
}
96
127
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 );
102
129
}
130
+
131
+ return $ bytes + $ this ->traversedBytes ;
103
132
}
104
133
105
134
private function pushIterable ($ iterable )
106
135
{
107
136
$ this ->generatorStack [] = $ this ->iterate ($ iterable );
108
137
$ this ->first = true ;
109
138
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
+ {
110
148
if ($ this ->options & JSON_FORCE_OBJECT ) {
111
- $ object = true ;
149
+ return true ;
112
150
} 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 );
117
152
}
118
153
119
- $ this ->outputLine ( $ object ? ' { ' : ' [ ' );
120
- $ this -> typeStack [] = $ object ;
154
+ $ generator = end ( $ this ->generatorStack );
155
+ return $ generator -> valid () && $ generator -> key () !== 0 ;
121
156
}
122
157
123
158
private function popIterable ()
@@ -129,7 +164,7 @@ private function popIterable()
129
164
$ this ->first = false ;
130
165
array_pop ($ this ->generatorStack );
131
166
$ object = array_pop ($ this ->typeStack );
132
- $ this ->output ($ object ? '} ' : '] ' );
167
+ return $ this ->output ($ object ? '} ' : '] ' );
133
168
}
134
169
135
170
public function traverseStack ()
@@ -141,7 +176,7 @@ public function traverseStack()
141
176
yield $ active ->key () => $ active ->current ();
142
177
$ active ->next ();
143
178
} else {
144
- $ this ->popIterable ();
179
+ $ this ->traversedBytes += $ this -> popIterable ();
145
180
}
146
181
}
147
182
}
@@ -155,42 +190,44 @@ public function iterate($iterable)
155
190
156
191
private function output ($ string )
157
192
{
193
+ $ bytes = 0 ;
194
+
158
195
if ($ this ->newLine && $ this ->options & JSON_PRETTY_PRINT ) {
159
- $ this ->write ("\n" );
196
+ $ bytes += $ this ->write ("\n" );
160
197
$ this ->line ++;
161
198
$ this ->column = 1 ;
162
- $ this ->write (str_repeat ($ this ->indent , count ($ this ->typeStack )));
199
+ $ bytes += $ this ->write (str_repeat ($ this ->indent , count ($ this ->typeStack )));
163
200
}
164
201
165
202
$ this ->newLine = false ;
166
- $ this ->write ($ string );
203
+ $ bytes += $ this ->write ($ string );
204
+
205
+ return $ bytes ;
167
206
}
168
207
169
208
private function outputLine ($ string )
170
209
{
171
- $ this ->output ($ string );
210
+ $ bytes = $ this ->output ($ string );
172
211
$ this ->newLine = true ;
212
+
213
+ return $ bytes ;
173
214
}
174
215
175
216
private function write ($ string )
176
217
{
177
218
echo $ string ;
219
+
178
220
$ this ->column += strlen ($ string );
221
+ return strlen ($ string );
179
222
}
180
223
181
224
private function encodeValue ($ value )
182
225
{
183
226
$ encoded = json_encode ($ value , $ this ->options );
184
227
185
228
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 ;
194
231
}
195
232
196
233
return $ encoded ;
0 commit comments