Skip to content

Commit

Permalink
include type checking and enhance json lists
Browse files Browse the repository at this point in the history
  • Loading branch information
dealfonso committed Jul 25, 2023
1 parent 1183591 commit 7d4421a
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 13 deletions.
35 changes: 34 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,4 +339,37 @@ class User extends JsonObject {
}
```

In this example, if we had not set the `birthDate` property but it is retrieved, it will be computed by subtracting the age to the current date.
In this example, if we had not set the `birthDate` property but it is retrieved, it will be computed by subtracting the age to the current date.

## Additional tools and technical facts

### Parsing a value

If wanted to parse an arbitrary object to a `JsonObject`, it is possible to use the function `JsonObject::parse_typed_value`. This is important to be able to convert from any type to a `JsonObject`-type.

e.g.

```php
$myobject = JsonObject::parse_typed_value("list[str]", [ "my", "name", "is", "John" ]);
```

Will obtain an object of type `JsonList<str>`.

### Type checking

The default behavior of this library is to ensure that the values set for the attributes match their defined type. But that means that would mean that, as a `float` is not an `int`, setting a float to `0` will fail because `0` is an integer. In that case, the user _must_ cast the values before assigning them. To control whether to so stritcly check the type or not, it is possible to use the constant `STRICT_TYPE_CHECKING`.

> If `STRICT_TYPE_CHECKING` it is set to `True`, the types will be strictly checked and e.g. assigning `9.3` to an `int` will raise an exception. If set to `False`, the numerical types will be converted from one to each other. So e.g. if we assign `9.3` to an `int` it will be automatically truncated to `9`.
Other important type checking is when assigning an empty value (i.e. `""` or `null`) to a numeric type. In that case, we have the constant `STRICT_TYPE_CHECKING_EMPTY_ZERO`.

> If `STRICT_TYPE_CHECKING_EMPTY_ZERO` is set to `True` (the default behavior), when assigning an empty value to a numeric type, it will be considered to be `0`. i.e. assigning an empty string or a `null` value to an `int` attribute, will mean to assign `0`. If set to `False`, the library will check the types and will eventually raise an exception.
### Enhanced JsonLists

Now `JsonList` also enables to use negative indexes, so that `-1` will be the last element, `-2` the penultimate, etc.

`JsonList` object includes functions for sorting or filtering.

- `public function sort(callable $callback = null) : JsonList`: sorts the list using the given callback. If no callback is given, it will sort the list using the default comparison function.
- `public function filter(callable $callback) : JsonList`: filters the list using the given callback. The callback must return a boolean value. If the callback returns `true`, the element will be included in the resulting list. If it returns `false`, the element will be discarded.
82 changes: 70 additions & 12 deletions src/JsonObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,16 @@

namespace ddn\jsonobject;

define('JSONOBJECT_VERSION', '0.1.1');
define('JSONOBJECT_VERSION', '0.2.0');

if (!defined('STRICT_TYPE_CHECKING')) {
define('STRICT_TYPE_CHECKING', false);
}

if (!defined('STRICT_TYPE_CHECKING_EMPTY_ZERO')) {
define('STRICT_TYPE_CHECKING_EMPTY_ZERO', true);
}

if (!defined('JSONOBJECT_DEBUGGING')) {
define('JSONOBJECT_DEBUGGING', false);
}
Expand Down Expand Up @@ -150,7 +154,7 @@ abstract class JsonBaseObject {
* @param $value mixed The value to be parsed
* @param $subtype string | null The type of the elements if type is list or dict
*/
protected static function parse_typed_value($type, $value, $subtype = null) {
public static function parse_typed_value($type, $value, $subtype = null) {
if ($value === null) {
return null;
}
Expand Down Expand Up @@ -338,10 +342,11 @@ public function count() : int {
/**
* Function that is used to validate that the offset has the proper type
*/
protected function _validateOffset(mixed $offset): void {
protected function _validateOffset(mixed $offset): mixed {
if (gettype($offset) != 'string') {
throw new \TypeError(sprintf('Offset must be of type string, %s given', gettype($offset)));
}
return $offset;
}

/**
Expand All @@ -355,23 +360,21 @@ public function __construct(mixed $type) {
* Returns true if the given offset has a value
*/
public function offsetExists(mixed $offset) : bool {
$this->_validateOffset($offset);
return array_key_exists($offset, $this->values);
return array_key_exists($this->_validateOffset($offset), $this->values);
}

/**
* Returns the value at the given offset
*/
public function offsetGet(mixed $offset) : mixed {
$this->_validateOffset($offset);
return $this->values[$offset];
return $this->values[$this->_validateOffset($offset)];
}

/**
* Sets the value at the given offset
*/
public function offsetSet(mixed $offset, mixed $value) : void {
$this->_validateOffset($offset);
$offset = $this->_validateOffset($offset);
if ($offset === null) {
$this->values[] = $value;
return;
Expand All @@ -383,8 +386,7 @@ public function offsetSet(mixed $offset, mixed $value) : void {
* Removes the value at the given offset
*/
public function offsetUnset(mixed $offset) : void {
$this->_validateOffset($offset);
unset($this->values[$offset]);
unset($this->values[$this->_validateOffset($offset)]);
}

/**
Expand Down Expand Up @@ -444,17 +446,35 @@ public function keys() : array {
public function values() : array {
return array_values($this->values);
}

/**
* Filters the dictionary
*/
public function filter(callable $callback) : JsonDict {
$class = get_called_class();
$object = new $class($this->type);
foreach ($this->values as $key => $value) {
if ($callback($value, $key)) {
$object[$key] = $value;
}
}
return $object;
}
}

class JsonList extends JsonDict {
/**
* Validates that the offset is an integer
* @param $offset mixed The offset to be validated
*/
protected function _validateOffset(mixed $offset): void {
protected function _validateOffset(mixed $offset): mixed {
if (!is_int($offset)) {
throw new \TypeError("Array keys must be integers");
}
if ($offset < 0) {
$offset = count($this->values) + $offset;
}
return $offset;
}

/**
Expand All @@ -477,6 +497,35 @@ public function offsetSet(mixed $offset, mixed $value) : void {
}
parent::offsetSet($offset, $value);
}

/**
* Filters the list
*/
public function filter(callable $callback) : JsonList {
$class = get_called_class();
$object = new $class($this->type);
foreach ($this->values as $key => $value) {
if ($callback($value, $key)) {
$object[] = $value;
}
}
return $object;
}

/**
* Sorts the list
*/
public function sort(callable $callback = null) : JsonList {
$class = get_called_class();
$object = new $class($this->type);
$object->values = $this->values;
if ($callback === null) {
sort($object->values);
} else {
usort($object->values, $callback);
}
return $object;
}
}

class JsonObject extends JsonBaseObject {
Expand Down Expand Up @@ -598,7 +647,7 @@ public function __construct(...$args) {
// We'll convert the value to the correct type
$value = $this->$name;
unset($this->$name);
$this->$name = self::parse_typed_value($definition['type'], $value);
$this->$name = self::parse_typed_value($definition['type'], $value, $definition['subtype']);
} else {
// If there is a default value, we'll use it
if ($definition['default'] !== null) {
Expand Down Expand Up @@ -656,6 +705,9 @@ public function __set($name, $value) {
if ((!STRICT_TYPE_CHECKING) && is_numeric($value)) {
$value = intval($value);
}
if ((STRICT_TYPE_CHECKING_EMPTY_ZERO) && ($value === "")) {
$value = 0;
}
if (!is_int($value)) {
throw new \InvalidArgumentException("Attribute $name must be an int");
}
Expand All @@ -664,6 +716,9 @@ public function __set($name, $value) {
if ((!STRICT_TYPE_CHECKING) && is_numeric($value)) {
$value = floatval($value);
}
if ((STRICT_TYPE_CHECKING_EMPTY_ZERO) && ($value === "")) {
$value = 0;
}
if (!is_float($value) && !is_int($value)) {
throw new \InvalidArgumentException("Attribute $name must be a float");
}
Expand All @@ -678,6 +733,9 @@ public function __set($name, $value) {
}
break;
case 'bool':
if ((STRICT_TYPE_CHECKING_EMPTY_ZERO) && ($value === "")) {
$value = false;
}
$value = (bool)($value == 0 ? false : true);
break;
case 'list':
Expand Down

0 comments on commit 7d4421a

Please sign in to comment.