Improvement converted json type
Is your feature request related to a problem?
Not a problem, a proposal for a convenience addition to \Psl\Json\typed conversion.
Describe the solution you'd like
A converted type function for json data (see below snippet) usable on convert shapes.
Describe alternatives you've considered Can be achieved with current Psl but is a bit more verbose
Snippet
/**
* @template D
* @template O
*
* @param TypeInterface<D> $decoded decoded json type
* @param TypeInterface<O> $into
* @param null|(Closure(D): O) $converter argument is the decoded json value of type $from
*
* @return TypeInterface<O>
*/
function convertedJson(TypeInterface $decoded, TypeInterface $into, ?\Closure $converter = null): TypeInterface
{
return new Internal\ConvertedType(
string(),
$into,
null === $converter
? static fn(string $json): mixed => $into->assert(typed($json, $decoded)) // if the decoded json type is the same as the 'into' type
: static fn(string $json): mixed => $converter(typed($json, $decoded))
);
}
Some examples:
In short: Converting a list of string id's to a collection of id's
<?php
final readonly class Id
{
public function __construct(public string $id)
{
}
public static function type(): TypeInterface
{
return converted(
string(),
instance_of(__CLASS__),
static fn(string $id) => new self($id)
);
}
}
final readonly class IdCollection
{
public function __construct(
public array $ids
) {
}
public static function type(): TypeInterface
{
return converted(
vec(
Id::type()
),
instance_of(__CLASS__),
static fn(array $ids) => new self($ids)
);
}
}
/** shape containing the full convert type */
shape([
'key' => converted(
string(),
instance_of(IdCollection::class),
static fn(string $json) => typed(
$json,
converted(
vec(
string()
),
instance_of(IdCollection::class),
static fn(array $ids): IdCollection => new IdCollection(
map(
$ids,
static fn(string $id): Id => new Id($id)
)
)
)
)
),
])->coerce(
['key' => '["id-one","id-two"]']
);
/** same with abstracted types (already possible with current Psl) */
shape([
'key' => converted(
string(),
instance_of(IdCollection::class),
static fn(string $json): IdCollection => typed(
$json,
IdCollection::type()
)
),
])->coerce(
['key' => '["id-one","id-two"]']
);
/** convertedJson with converter Closure */
shape([
'key' => convertedJson(
IdCollection::type(),
instance_of(IdCollection::class),
static fn(IdCollection $collection): IdCollection => $collection
),
])->coerce(
['key' => '["id-one","id-two"]']
);
/** convertedJson without converter */
$shape = shape([
'key' => convertedJson(
IdCollection::type(),
instance_of(IdCollection::class),
),
])->coerce(
['key' => '["id-one","id-two"]']
);
Penny for your thoughts on this
Something like this could work:
namespace Psl\Type;
use Psl\Json;
/**
* @psalm-pure
*
* @template T
*
* @param TypeInterface<T> $value_type
*
* @return TypeInterface<T>
*/
function json(TypeInterface $value_type): TypeInterface
{
return converted(
non_empty_string(),
$value_type,
static fn (string $json): mixed => Json\typed($json, $value_type)
);
}
Where you could do:
Type\Json(IdCollection::type())
Having an additional callback would be a bridge too far for me. You could always do that in your IdCollection::type() which already contains a converted type.
@azjezz WDYT?
In the future we could introduce similar converted shortcuts like these, e.g. for Type\date_time($format, $timezone) without having to specifying a dedicated internal type for them?
@veewee that would indeed do the job.
@azjezz WDYT?
I like the idea, but maybe a different name than Type\json? something like Type\from_json() that indicate that the value would come from json?