Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Normalize set values when passing objects #9

Merged
merged 13 commits into from
Dec 9, 2020
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ jobs:
build:
environment:
CC_TEST_REPORTER_ID: 8ec926841c6dfead9c848fd063c569e11b06be11442a8175d588e10607ee2150
XDEBUG_MODE: coverage
docker:
- image: circleci/php:7-cli-node-browsers-legacy
working_directory: ~/repo
Expand Down
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
vendor/
.phpunit*
.vscode/
/.idea/
/.idea/codeStyles/codeStyleConfig.xml
/composer.lock
/.idea/modules.xml
/.idea/php.xml
/.idea/RootedJsonData.iml
/.idea/vcs.xml
/.idea/workspace.xml
78 changes: 59 additions & 19 deletions src/RootedJsonData.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,41 +31,40 @@ class RootedJsonData
*/
public function __construct(string $json = "{}", string $schema = "{}")
{
$decoded = json_decode($json);
Copy link
Member Author

@dafeder dafeder Dec 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validate before doing anything else. Validate JSON string directly, JsonObject will result in an empty object ({}) being transformed into an array ([]).


if (!isset($decoded)) {
throw new InvalidArgumentException("Invalid JSON: " . json_last_error_msg());
}

if (Schema::fromJsonString($schema)) {
$this->schema = $schema;
}

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

$this->data = $data;
$this->data = new JsonObject($json, true);
}

/**
* Validate a JsonObject.
* Validate JSON.
*
* @param JsonObject $data
* JsonData object to validate against schema.
* @param string $json
* JSON string to validate against schema.
* @param string $schema
* JSON Schema string.
*
* @return ValidationResult
* Validation result object, contains error report if invalid.
*/
public static function validate(JsonObject $data, string $schema): ValidationResult
public static function validate(string $json, string $schema): ValidationResult
{
$decoded = json_decode($json);

if (!isset($decoded)) {
throw new InvalidArgumentException("Invalid JSON: " . json_last_error_msg());
}

$opiSchema = Schema::fromJsonString($schema);
$validator = new Validator();
return $validator->schemaValidation(json_decode("{$data}"), $opiSchema);
return $validator->schemaValidation($decoded, $opiSchema);
}

/**
Expand All @@ -75,7 +74,17 @@ public static function validate(JsonObject $data, string $schema): ValidationRes
*/
public function __toString()
{
return (string) $this->data;
return $this->data->getJson();
}

/**
* Return pretty-formatted JSON string
*
* @return string
*/
public function pretty()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method for returning formatted JSON

{
return $this->data->getJson(JSON_PRETTY_PRINT);
}

/**
Expand Down Expand Up @@ -103,7 +112,7 @@ public function get(string $path)
*/
public function __get(string $path)
{
return $this->data->get($path);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reset the magic getter

return $this->get($path);
}

/**
Expand All @@ -117,6 +126,7 @@ public function __get(string $path)
*/
public function set(string $path, $value)
{
$this->normalizeSetValue($value);
$validationJsonObject = new JsonObject((string) $this->data);
$validationJsonObject->set($path, $value);

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

/**
* Ensure consistent data type whether RootedJsonData or stdClass.
*
* @param mixed $value
*/
private function normalizeSetValue(&$value)
{
if ($value instanceof RootedJsonData) {
$value = $value->{"$"};
}
if ($value instanceof \stdClass) {
$value = new RootedJsonData(json_encode($value));
$this->normalizeSetValue($value);
}
}

/**
* @see \JsonPath\JsonObject::__get()
*
Expand All @@ -140,15 +166,29 @@ public function set(string $path, $value)
*/
public function __set($path, $value)
{
return $this->data->set($path, $value);
return $this->set($path, $value);
}

public function __isset($name)
/**
* Magic __isset method for a path.
*
* @param mixed $path
* Check if a property at this path is set or not.
*
* @return bool
*/
public function __isset($path)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consistent variable names

{
$notSmart = new JsonObject("{$this->data}");
return $notSmart->get($name) ? true : false;
return $notSmart->get($path) ? true : false;
}

/**
* Get the JSON Schema as a string.
*
* @return string
* The JSON Schema for this object.
*/
public function getSchema()
{
return $this->schema;
Expand Down
55 changes: 52 additions & 3 deletions tests/RootedJsonDatatTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

class RootedJsonDataTest extends TestCase
{
public function testSeamlessExperience()
public function testJsonInOut()
{
$data = new RootedJsonData();
$data->set("$.title", "Hello");
Expand Down Expand Up @@ -88,22 +88,36 @@ public function testJsonIntegrityFailureAfterChange()
$json = '{"number":51}';
$schema = '{"type":"object","properties": {"number":{ "type":"number"}}}';
$data = new RootedJsonData($json, $schema);
$this->assertEquals($json, "{$data}");

$data->set("$.number", "Alice");
}

// Test with magic setter as well.
/**
* Do schemas still work with magic setter?
*/
public function testJsonIntegrityFailureMagicSetter()
{
$this->expectExceptionMessage("\$[number] expects a number");

$json = '{"number":51}';
$schema = '{"type":"object","properties": {"number":{ "type":"number"}}}';
$data = new RootedJsonData($json, $schema);
$data->{"$[number]"} = "Alice";
}

/**
* Simple get value from JSON path.
*/
public function testJsonPathGetter()
{
$json = '{"container":{"number":51}}';
$data = new RootedJsonData($json);
$this->assertEquals(51, $data->get("$.container.number"));
}

/**
* Simple set by JSON path.
*/
public function testJsonPathSetter()
{
$json = '{"container":{"number":51}}';
Expand All @@ -112,11 +126,46 @@ public function testJsonPathSetter()
$this->assertEquals(52, $data->get("$.container.number"));
}

/**
* Adding JSON structures in multiple formats should have predictable results.
*/
public function testAddJsonData()
{
// Test adding RootedJsonData structure.
$json = '{}';
$containerSchema = '{"type":"object","properties":{"number":{"type":"number"}}}';
$schema = '{"type":"object","properties":{"container":'.$containerSchema.'}}';
$subJson = '{"number":51}';
$data = new RootedJsonData($json, $schema);
$data->set("$.container", new RootedJsonData($subJson));
$this->assertEquals(51, $data->get("$.container.number"));

// If we add stdClass object, it should be work and be an array.
$data2 = new RootedJsonData($json, $schema);
$data2->set("$.container", json_decode($subJson));
$this->assertEquals(51, $data2->get("$.container.number"));
$this->assertIsArray($data2->get("$.container"));
}

/**
* getSchema() should return the same string that was provided to constructor.
*/
public function testSchemaGetter()
{
$json = '{"number":51}';
$schema = '{"type": "object","properties":{"number":{"type":"number"}}}';
$data = new RootedJsonData($json, $schema);
$this->assertEquals($schema, $data->getSchema());
}

/**
* Regular string should be one line, pretty() should return multiple lines.
*/
public function testPretty()
{
$json = '{"number":51}';
$data = new RootedJsonData($json);
$this->assertEquals(0, substr_count("$data", "\n"));
$this->assertEquals(2, substr_count($data->pretty(), "\n"));
}
}