Skip to content

Commit bf203e0

Browse files
committed
Make serialization of integer keys consistent with JSON.stringify() in JS
1 parent 7e08256 commit bf203e0

File tree

4 files changed

+65
-63
lines changed

4 files changed

+65
-63
lines changed

ChangeLog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ JSON for the XP Framework ChangeLog
55

66
## 6.0.0 / ????-??-??
77

8+
* Made serialization of integer keys consistent with `JSON.stringify()`
9+
(@thekid)
810
* Merged PR #16: Return `{}` as empty object instead of empty array
911
(@thekid)
1012

src/main/php/text/json/Format.class.php

Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php namespace text\json;
22

3-
use StdClass;
3+
use StdClass, Traversable;
44
use lang\{IllegalArgumentException, Value};
55

66
/**
@@ -81,52 +81,75 @@ public function close($token) {
8181
* @return string
8282
*/
8383
public function representationOf($value) {
84-
$t= gettype($value);
85-
if ('string' === $t) {
86-
return json_encode($value, $this->options);
87-
} else if ('integer' === $t) {
88-
return (string)$value;
89-
} else if ('double' === $t) {
84+
$r= '';
85+
foreach ($this->tokensOf($value) as $bytes) {
86+
$r.= $bytes;
87+
}
88+
return $r;
89+
}
90+
91+
/**
92+
* Yields tokens for a given value
93+
*
94+
* @param var $value
95+
* @return iterable
96+
*/
97+
public function tokensOf($value) {
98+
if (is_string($value)) {
99+
yield json_encode($value, $this->options);
100+
} else if (is_int($value)) {
101+
yield (string)$value;
102+
} else if (is_float($value)) {
90103
$cast= (string)$value;
91-
return strpos($cast, '.') ? $cast : $cast.'.0';
92-
} else if ('array' === $t) {
104+
yield strpos($cast, '.') ? $cast : $cast.'.0';
105+
} else if (is_array($value)) {
93106
if (empty($value)) {
94-
return '[]';
107+
yield '[]';
95108
} else if (0 === key($value)) {
96-
$r= $this->open('[');
97-
$next= false;
109+
yield $this->open('[');
110+
$i= 0;
98111
foreach ($value as $element) {
99-
if ($next) {
100-
$r.= $this->comma;
101-
} else {
102-
$next= true;
103-
}
104-
$r.= $this->representationOf($element);
112+
if ($i++) yield $this->comma;
113+
yield from $this->tokensOf($element);
105114
}
106-
return $r.$this->close(']');
107-
} else { map:
108-
$r= $this->open('{');
109-
$next= false;
110-
foreach ($value as $key => $mapped) {
111-
if ($next) {
112-
$r.= $this->comma;
113-
} else {
114-
$next= true;
115-
}
116-
$r.= $this->representationOf($key).$this->colon.$this->representationOf($mapped);
115+
yield $this->close(']');
116+
} else {
117+
map: yield $this->open('{');
118+
$i= 0;
119+
foreach ($value as $key => $element) {
120+
if ($i++) yield $this->comma;
121+
yield from $this->tokensOf((string)$key);
122+
yield $this->colon;
123+
yield from $this->tokensOf($element);
117124
}
118-
return $r.$this->close('}');
125+
yield $this->close('}');
119126
}
120127
} else if (null === $value) {
121-
return 'null';
128+
yield 'null';
122129
} else if (true === $value) {
123-
return 'true';
130+
yield 'true';
124131
} else if (false === $value) {
125-
return 'false';
126-
} else if ($value instanceof StdClass) {
127-
$value= (array)$value;
128-
if (empty($value)) return '{}';
132+
yield 'false';
133+
} else if ($value instanceof JsonObject || $value instanceof StdClass) {
129134
goto map;
135+
} else if ($value instanceof Traversable) {
136+
$i= 0;
137+
$map= null;
138+
foreach ($value as $key => $element) {
139+
if (0 === $i++) {
140+
$map= 0 !== $key;
141+
yield $this->open($map ? '{' : '[');
142+
} else {
143+
yield $this->comma;
144+
}
145+
146+
if ($map) {
147+
yield from $this->tokensOf((string)$key);
148+
yield $this->colon;
149+
}
150+
yield from $this->tokensOf($element);
151+
}
152+
yield null === $map ? '[]' : $this->close($map ? '}' : ']');
130153
} else {
131154
throw new IllegalArgumentException('Cannot represent instances of '.typeof($value));
132155
}

src/main/php/text/json/Output.class.php

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
<?php namespace text\json;
22

3-
use Traversable;
43
use lang\{IllegalArgumentException, Value};
54

65
abstract class Output implements Value {
@@ -22,30 +21,8 @@ public function __construct($format= null) {
2221
* @return self
2322
*/
2423
public function write($value) {
25-
$f= $this->format;
26-
if ($value instanceof Traversable || is_array($value)) {
27-
$i= 0;
28-
$map= null;
29-
foreach ($value as $key => $element) {
30-
if (0 === $i++) {
31-
$map= 0 !== $key;
32-
$this->appendToken($f->open($map ? '{' : '['));
33-
} else {
34-
$this->appendToken($f->comma);
35-
}
36-
37-
if ($map) {
38-
$this->appendToken($f->representationOf((string)$key).$f->colon);
39-
}
40-
$this->write($element);
41-
}
42-
if (null === $map) {
43-
$this->appendToken('[]');
44-
} else {
45-
$this->appendToken($f->close($map ? '}' : ']'));
46-
}
47-
} else {
48-
$this->appendToken($f->representationOf($value));
24+
foreach ($this->format->tokensOf($value) as $token) {
25+
$this->appendToken($token);
4926
}
5027
return $this;
5128
}

src/test/php/text/json/unittest/JsonOutputTest.class.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,12 @@ public function write_empty_object() {
118118

119119
#[Test]
120120
public function write_array_as_object() {
121-
Assert::equals('{0:1,1:2,2:3}', $this->write((object)[1, 2, 3]));
121+
Assert::equals('{"0":1,"1":2,"2":3}', $this->write((object)[1, 2, 3]));
122122
}
123123

124124
#[Test]
125125
public function write_nested_array_as_object() {
126-
Assert::equals('{"values":{0:1,1:2,2:3}}', $this->write(['values' => (object)[1, 2, 3]]));
126+
Assert::equals('{"values":{"0":1,"1":2,"2":3}}', $this->write(['values' => (object)[1, 2, 3]]));
127127
}
128128

129129
#[Test]

0 commit comments

Comments
 (0)