Skip to content

Commit ea187f9

Browse files
authored
Normalize set values when passing objects (#9)
1 parent cf5d492 commit ea187f9

File tree

4 files changed

+118
-23
lines changed

4 files changed

+118
-23
lines changed

.circleci/config.yml

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ jobs:
33
build:
44
environment:
55
CC_TEST_REPORTER_ID: 8ec926841c6dfead9c848fd063c569e11b06be11442a8175d588e10607ee2150
6+
XDEBUG_MODE: coverage
67
docker:
78
- image: circleci/php:7-cli-node-browsers-legacy
89
working_directory: ~/repo

.gitignore

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
vendor/
22
.phpunit*
33
.vscode/
4-
/.idea/
4+
/.idea/codeStyles/codeStyleConfig.xml
55
/composer.lock
6+
/.idea/modules.xml
7+
/.idea/php.xml
8+
/.idea/RootedJsonData.iml
9+
/.idea/vcs.xml
10+
/.idea/workspace.xml

src/RootedJsonData.php

+59-19
Original file line numberDiff line numberDiff line change
@@ -31,41 +31,40 @@ class RootedJsonData
3131
*/
3232
public function __construct(string $json = "{}", string $schema = "{}")
3333
{
34-
$decoded = json_decode($json);
35-
36-
if (!isset($decoded)) {
37-
throw new InvalidArgumentException("Invalid JSON: " . json_last_error_msg());
38-
}
39-
4034
if (Schema::fromJsonString($schema)) {
4135
$this->schema = $schema;
4236
}
4337

44-
$data = new JsonObject($json, true);
45-
$result = self::validate($data, $this->schema);
38+
$result = self::validate($json, $this->schema);
4639
if (!$result->isValid()) {
4740
throw new ValidationException("JSON Schema validation failed.", $result);
4841
}
4942

50-
$this->data = $data;
43+
$this->data = new JsonObject($json, true);
5144
}
5245

5346
/**
54-
* Validate a JsonObject.
47+
* Validate JSON.
5548
*
56-
* @param JsonObject $data
57-
* JsonData object to validate against schema.
49+
* @param string $json
50+
* JSON string to validate against schema.
5851
* @param string $schema
5952
* JSON Schema string.
6053
*
6154
* @return ValidationResult
6255
* Validation result object, contains error report if invalid.
6356
*/
64-
public static function validate(JsonObject $data, string $schema): ValidationResult
57+
public static function validate(string $json, string $schema): ValidationResult
6558
{
59+
$decoded = json_decode($json);
60+
61+
if (!isset($decoded)) {
62+
throw new InvalidArgumentException("Invalid JSON: " . json_last_error_msg());
63+
}
64+
6665
$opiSchema = Schema::fromJsonString($schema);
6766
$validator = new Validator();
68-
return $validator->schemaValidation(json_decode("{$data}"), $opiSchema);
67+
return $validator->schemaValidation($decoded, $opiSchema);
6968
}
7069

7170
/**
@@ -75,7 +74,17 @@ public static function validate(JsonObject $data, string $schema): ValidationRes
7574
*/
7675
public function __toString()
7776
{
78-
return (string) $this->data;
77+
return $this->data->getJson();
78+
}
79+
80+
/**
81+
* Return pretty-formatted JSON string
82+
*
83+
* @return string
84+
*/
85+
public function pretty()
86+
{
87+
return $this->data->getJson(JSON_PRETTY_PRINT);
7988
}
8089

8190
/**
@@ -103,7 +112,7 @@ public function get(string $path)
103112
*/
104113
public function __get(string $path)
105114
{
106-
return $this->data->get($path);
115+
return $this->get($path);
107116
}
108117

109118
/**
@@ -117,6 +126,7 @@ public function __get(string $path)
117126
*/
118127
public function set(string $path, $value)
119128
{
129+
$this->normalizeSetValue($value);
120130
$validationJsonObject = new JsonObject((string) $this->data);
121131
$validationJsonObject->set($path, $value);
122132

@@ -130,6 +140,22 @@ public function set(string $path, $value)
130140
return $this->data->set($path, $value);
131141
}
132142

143+
/**
144+
* Ensure consistent data type whether RootedJsonData or stdClass.
145+
*
146+
* @param mixed $value
147+
*/
148+
private function normalizeSetValue(&$value)
149+
{
150+
if ($value instanceof RootedJsonData) {
151+
$value = $value->{"$"};
152+
}
153+
if ($value instanceof \stdClass) {
154+
$value = new RootedJsonData(json_encode($value));
155+
$this->normalizeSetValue($value);
156+
}
157+
}
158+
133159
/**
134160
* @see \JsonPath\JsonObject::__get()
135161
*
@@ -140,15 +166,29 @@ public function set(string $path, $value)
140166
*/
141167
public function __set($path, $value)
142168
{
143-
return $this->data->set($path, $value);
169+
return $this->set($path, $value);
144170
}
145171

146-
public function __isset($name)
172+
/**
173+
* Magic __isset method for a path.
174+
*
175+
* @param mixed $path
176+
* Check if a property at this path is set or not.
177+
*
178+
* @return bool
179+
*/
180+
public function __isset($path)
147181
{
148182
$notSmart = new JsonObject("{$this->data}");
149-
return $notSmart->get($name) ? true : false;
183+
return $notSmart->get($path) ? true : false;
150184
}
151185

186+
/**
187+
* Get the JSON Schema as a string.
188+
*
189+
* @return string
190+
* The JSON Schema for this object.
191+
*/
152192
public function getSchema()
153193
{
154194
return $this->schema;

tests/RootedJsonDatatTest.php

+52-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
class RootedJsonDataTest extends TestCase
1313
{
14-
public function testSeamlessExperience()
14+
public function testJsonInOut()
1515
{
1616
$data = new RootedJsonData();
1717
$data->set("$.title", "Hello");
@@ -88,22 +88,36 @@ public function testJsonIntegrityFailureAfterChange()
8888
$json = '{"number":51}';
8989
$schema = '{"type":"object","properties": {"number":{ "type":"number"}}}';
9090
$data = new RootedJsonData($json, $schema);
91-
$this->assertEquals($json, "{$data}");
9291

9392
$data->set("$.number", "Alice");
93+
}
9494

95-
// Test with magic setter as well.
95+
/**
96+
* Do schemas still work with magic setter?
97+
*/
98+
public function testJsonIntegrityFailureMagicSetter()
99+
{
96100
$this->expectExceptionMessage("\$[number] expects a number");
101+
102+
$json = '{"number":51}';
103+
$schema = '{"type":"object","properties": {"number":{ "type":"number"}}}';
104+
$data = new RootedJsonData($json, $schema);
97105
$data->{"$[number]"} = "Alice";
98106
}
99107

108+
/**
109+
* Simple get value from JSON path.
110+
*/
100111
public function testJsonPathGetter()
101112
{
102113
$json = '{"container":{"number":51}}';
103114
$data = new RootedJsonData($json);
104115
$this->assertEquals(51, $data->get("$.container.number"));
105116
}
106117

118+
/**
119+
* Simple set by JSON path.
120+
*/
107121
public function testJsonPathSetter()
108122
{
109123
$json = '{"container":{"number":51}}';
@@ -112,11 +126,46 @@ public function testJsonPathSetter()
112126
$this->assertEquals(52, $data->get("$.container.number"));
113127
}
114128

129+
/**
130+
* Adding JSON structures in multiple formats should have predictable results.
131+
*/
132+
public function testAddJsonData()
133+
{
134+
// Test adding RootedJsonData structure.
135+
$json = '{}';
136+
$containerSchema = '{"type":"object","properties":{"number":{"type":"number"}}}';
137+
$schema = '{"type":"object","properties":{"container":'.$containerSchema.'}}';
138+
$subJson = '{"number":51}';
139+
$data = new RootedJsonData($json, $schema);
140+
$data->set("$.container", new RootedJsonData($subJson));
141+
$this->assertEquals(51, $data->get("$.container.number"));
142+
143+
// If we add stdClass object, it should be work and be an array.
144+
$data2 = new RootedJsonData($json, $schema);
145+
$data2->set("$.container", json_decode($subJson));
146+
$this->assertEquals(51, $data2->get("$.container.number"));
147+
$this->assertIsArray($data2->get("$.container"));
148+
}
149+
150+
/**
151+
* getSchema() should return the same string that was provided to constructor.
152+
*/
115153
public function testSchemaGetter()
116154
{
117155
$json = '{"number":51}';
118156
$schema = '{"type": "object","properties":{"number":{"type":"number"}}}';
119157
$data = new RootedJsonData($json, $schema);
120158
$this->assertEquals($schema, $data->getSchema());
121159
}
160+
161+
/**
162+
* Regular string should be one line, pretty() should return multiple lines.
163+
*/
164+
public function testPretty()
165+
{
166+
$json = '{"number":51}';
167+
$data = new RootedJsonData($json);
168+
$this->assertEquals(0, substr_count("$data", "\n"));
169+
$this->assertEquals(2, substr_count($data->pretty(), "\n"));
170+
}
122171
}

0 commit comments

Comments
 (0)