Skip to content

Commit 0f69f0e

Browse files
Fix objects are non unique despite key order (#819)
As pointed out by the [bowtie report](https://bowtie.report/#/dialects/draft4?language=php) there where some issues when validating the uniqueness of values espec. when the order of the keys was different.
1 parent 35d262c commit 0f69f0e

File tree

3 files changed

+74
-67
lines changed

3 files changed

+74
-67
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
## [Unreleased]
9+
### Fixed
10+
- Fix objects are non-unique despite key order ([#819](https://github.com/jsonrainbow/json-schema/pull/819))
911

1012
## [6.4.1] - 2025-04-04
1113
### Fixed

src/JsonSchema/Constraints/CollectionConstraint.php

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use JsonSchema\ConstraintError;
1515
use JsonSchema\Entity\JsonPointer;
16+
use JsonSchema\Tool\DeepComparer;
1617

1718
/**
1819
* The CollectionConstraint Constraints, validates an array against a given schema
@@ -39,14 +40,14 @@ public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = n
3940

4041
// Verify uniqueItems
4142
if (isset($schema->uniqueItems) && $schema->uniqueItems) {
42-
$unique = $value;
43-
if (is_array($value) && count($value)) {
44-
$unique = array_map(function ($e) {
45-
return var_export($e, true);
46-
}, $value);
47-
}
48-
if (count(array_unique($unique)) != count($value)) {
49-
$this->addError(ConstraintError::UNIQUE_ITEMS(), $path);
43+
$count = count($value);
44+
for ($x = 0; $x < $count - 1; $x++) {
45+
for ($y = $x + 1; $y < $count; $y++) {
46+
if (DeepComparer::isEqual($value[$x], $value[$y])) {
47+
$this->addError(ConstraintError::UNIQUE_ITEMS(), $path);
48+
break 2;
49+
}
50+
}
5051
}
5152
}
5253

tests/Constraints/UniqueItemsTest.php

Lines changed: 63 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -16,142 +16,146 @@ class UniqueItemsTest extends BaseTestCase
1616
public function getInvalidTests(): array
1717
{
1818
return [
19-
[
20-
'[1,2,2]',
21-
'{
19+
'Non unique integers' => [
20+
'input' => '[1,2,2]',
21+
'schema' => '{
2222
"type":"array",
2323
"uniqueItems": true
2424
}'
2525
],
26-
[
27-
'[{"a":"b"},{"a":"c"},{"a":"b"}]',
28-
'{
26+
'Non unique objects' => [
27+
'input' => '[{"a":"b"},{"a":"c"},{"a":"b"}]',
28+
'schema' => '{
2929
"type":"array",
3030
"uniqueItems": true
3131
}'
3232
],
33-
[
34-
'[{"foo": {"bar" : {"baz" : true}}}, {"foo": {"bar" : {"baz" : true}}}]',
35-
'{
33+
'Non unique objects - three levels deep' => [
34+
'input' => '[{"foo": {"bar" : {"baz" : true}}}, {"foo": {"bar" : {"baz" : true}}}]',
35+
'schema' => '{
3636
"type": "array",
3737
"uniqueItems": true
3838
}'
3939
],
40-
[
41-
'[1.0, 1.00, 1]',
42-
'{
40+
'Non unique mathematical values for the number one' => [
41+
'input' => '[1.0, 1.00, 1]',
42+
'schema' => '{
4343
"type": "array",
4444
"uniqueItems": true
4545
}'
4646
],
47-
[
48-
'[["foo"], ["foo"]]',
49-
'{
47+
'Non unique arrays' => [
48+
'input' => '[["foo"], ["foo"]]',
49+
'schema' => '{
5050
"type": "array",
5151
"uniqueItems": true
5252
}'
5353
],
54-
[
55-
'[{}, [1], true, null, {}, 1]',
56-
'{
54+
'Non unique mix of different types' => [
55+
'input' => '[{}, [1], true, null, {}, 1]',
56+
'schema' => '{
5757
"type": "array",
5858
"uniqueItems": true
5959
}'
60+
],
61+
'objects are non-unique despite key order' => [
62+
'input' => '[{"a": 1, "b": 2}, {"b": 2, "a": 1}]',
63+
'schema' => '{"uniqueItems": true}',
6064
]
6165
];
6266
}
6367

6468
public function getValidTests(): array
6569
{
6670
return [
67-
[
68-
'[1,2,3]',
69-
'{
70-
"type":"array",
71-
"uniqueItems": true
71+
'unique integers' => [
72+
'input' => '[1,2,3]',
73+
'schema' => '{
74+
"type":"array",
75+
"uniqueItems": true
7276
}'
7377
],
74-
[
75-
'[{"foo": 12}, {"bar": false}]',
76-
'{
78+
'unique objects' =>[
79+
'input' => '[{"foo": 12}, {"bar": false}]',
80+
'schema' => '{
7781
"type": "array",
7882
"uniqueItems": true
7983
}'
8084
],
81-
[
82-
'[1, true]',
83-
'{
85+
'Integer one and boolean true' => [
86+
'input' => '[1, true]',
87+
'schema' => '{
8488
"type": "array",
8589
"uniqueItems": true
8690
}'
8791
],
88-
[
89-
'[0, false]',
90-
'{
92+
'Integer zero and boolean false' => [
93+
'input' => '[0, false]',
94+
'schema' => '{
9195
"type": "array",
9296
"uniqueItems": true
9397
}'
9498
],
95-
[
96-
'[{"foo": {"bar" : {"baz" : true}}}, {"foo": {"bar" : {"baz" : false}}}]',
97-
'{
99+
'Objects with different value three levels deep' => [
100+
'input' => '[{"foo": {"bar" : {"baz" : true}}}, {"foo": {"bar" : {"baz" : false}}}]',
101+
'schema' => '{
98102
"type": "array",
99103
"uniqueItems": true
100104
}'
101105
],
102-
[
103-
'[["foo"], ["bar"]]',
104-
'{
106+
'Array of strings' => [
107+
'input' => '[["foo"], ["bar"]]',
108+
'schema' => '{
105109
"type": "array",
106110
"uniqueItems": true
107111
}'
108112
],
109-
[
110-
'[{}, [1], true, null, 1]',
111-
'{
113+
'Object, Array, boolean, null and integer' => [
114+
'input' => '[{}, [1], true, null, 1]',
115+
'schema' => '{
112116
"type": "array",
113117
"uniqueItems": true
114118
}'
115119
],
116120
// below equals the invalid tests, but with uniqueItems set to false
117-
[
118-
'[1,2,2]',
119-
'{
121+
'Non unique integers' => [
122+
'input' => '[1,2,2]',
123+
'schema' => '{
120124
"type":"array",
121125
"uniqueItems": false
122126
}'
123127
],
124-
[
125-
'[{"a":"b"},{"a":"c"},{"a":"b"}]',
126-
'{
128+
'Non unique objects' => [
129+
'input' => '[{"a":"b"},{"a":"c"},{"a":"b"}]',
130+
'schema' => '{
127131
"type":"array",
128132
"uniqueItems": false
129133
}'
130134
],
131-
[
132-
'[{"foo": {"bar" : {"baz" : true}}}, {"foo": {"bar" : {"baz" : true}}}]',
133-
'{
135+
'Non unique objects - three levels deep' => [
136+
'input' => '[{"foo": {"bar" : {"baz" : true}}}, {"foo": {"bar" : {"baz" : true}}}]',
137+
'schema' => '{
134138
"type": "array",
135139
"uniqueItems": false
136140
}'
137141
],
138-
[
139-
'[1.0, 1.00, 1]',
140-
'{
142+
'Non unique mathematical values for the number one' => [
143+
'input' => '[1.0, 1.00, 1]',
144+
'schema' => '{
141145
"type": "array",
142146
"uniqueItems": false
143147
}'
144148
],
145-
[
146-
'[["foo"], ["foo"]]',
147-
'{
149+
'Non unique arrays' => [
150+
'input' => '[["foo"], ["foo"]]',
151+
'schema' => '{
148152
"type": "array",
149153
"uniqueItems": false
150154
}'
151155
],
152-
[
153-
'[{}, [1], true, null, {}, 1]',
154-
'{
156+
'Non unique mix of different types' => [
157+
'input' => '[{}, [1], true, null, {}, 1]',
158+
'schema' => '{
155159
"type": "array",
156160
"uniqueItems": false
157161
}'

0 commit comments

Comments
 (0)