Skip to content

Commit

Permalink
Normalize set values when passing objects (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
dafeder authored Dec 9, 2020
1 parent cf5d492 commit ea187f9
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 23 deletions.
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);

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()
{
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);
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)
{
$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"));
}
}

0 comments on commit ea187f9

Please sign in to comment.