-
Notifications
You must be signed in to change notification settings - Fork 26
Ruleset request DraftFourLoosely for loosely typed numbers #123
Comments
Hello, I think there are a few way to solve this. One option would be to modify your schema to allow loose types. This has the benefit of making it explicit that you allow loose types and working with other libraries. For instance, if you wanted to allow numeric strings: {
"type": ["string", "number"],
"pattern": "^\\d+$"
} Alternatively we could extend the validator to coerce types. AJV has a good implementation of this feature. I think this feature would be generally useful for situations like loading existing data that wasn't validated from a database or validating form input. I would be careful making just the type constraint use loose comparisons because it may have unintended consequences for other constraints. For instance, the If you do want to extend the existing constraint I prefer to add my custom logic and then defer to the existing constraint if the logic doesn't apply instead of inheriting from it, like this: <?php
class LooseComparisonType implements ConstraintInterface
{
public function __construct(DraftFour\type $type = null)
{
$this->type = $type ?: new DraftFour\Type();
}
public function validate($value, $type, Validator $validator)
{
if (is_numeric($value) && $type === 'number') {
return null;
}
return $this->type->validate($value, $type, $validator);
}
} |
Thanks a lot for the very helpful input. Modifying the schema is a simple and good idea to quickly solve the problem, but I'd like to avoid this. I already have After reading the documentation for coercing types, I believe this might be the better solution. And using composition instead of inheritance is definitely a good option. I changed my implementation and came up with the following solution: <?php
final class CoerceType implements \League\JsonGuard\ConstraintInterface
{
const KEYWORD = 'type';
private $typeConstraint;
public function __construct()
{
$this->typeConstraint = new \League\JsonGuard\Constraint\DraftFour\Type();
}
/**
* {@inheritdoc}
*/
public function validate($value, $typeList, \League\JsonGuard\Validator $validator)
{
$error = null;
foreach ((array)$typeList as $type) {
$value = $this->toCoercedType($value, $type);
$error = $this->typeConstraint->validate($value, $type, $validator);
if (is_null($error)) {
return null;
}
}
return $error;
}
private function toCoercedType($value, $type)
{
if ($type === 'number' && (!is_int($value) || !is_float($value))) {
$value = $this->toNumber($value);
} elseif ($type === 'integer' && !is_int($value)) {
$value = $this->toNumber($value);
} elseif ($type === 'boolean' && ($value !== true || $value !== false)) {
$value = $this->toBoolean($value);
}
return $value;
}
private function toNumber($value)
{
if ($value === true) {
$value = 1;
} elseif ($value === false) {
$value = 0;
} elseif (is_numeric($value)) {
$value = (int)$value;
}
return $value;
}
private function toBoolean($value)
{
return $value ? true : false;
}
} And implemented it like this: $ruleset = new \League\JsonGuard\RuleSet\DraftFour();
$ruleset->set('type', CoerceType::class);
$validator = new \League\JsonGuard\Validator($data, $schema, $ruleset); This implementation is far from exhaustive but it passes all the unit tests of my application. I took a deep look into AJV implementation, but I am not sure, if converting everything is a good solution. My intention would be more like imitating the PHP behavior of being a loosely typed language. |
I have a problem updating json-guard concerning the fixes in issue #68 and #69 where a strict typing for validating numbers is introduced ("is_int" instead "is_numeric"). I agree, that this is the right way, but PHP is a loosely typed language and it has some drivers which do not care very well for strict typing (like the MySQL driver).
In one of my application I have introduced a small variation of the DraftFour RuleSetContainer with a different "Type" class, like "DraftFourLoosely" and "TypeLoosely". But before writing a PR I'd like to know if something like that would be accepted or if there might be a better solution for this problem?
Copying the "Type" class for this behavior does not look good, too. Would it be acceptable to remove the "final" and "private" keywords there?
The text was updated successfully, but these errors were encountered: