jaspr / mapper
JSON API implementation, by annotations or schemas.
Requires
- php: ^8.2
- ext-json: *
- composer/class-map-generator: ^1.0
- composer/composer: ^2.7
- fig/http-message-util: ^1.1
- jaspr/expression: ^1.1.4
- jaspr/oas-builder: ^1.1.2
- opis/json-schema: ^2.3
- psr/http-factory: ^1.0
- psr/http-server-middleware: ^1.0
- psr/log: ^1.0 || ^2.0 || ^3.0
- psr/simple-cache: ^1.0 || ^2.0 || ^3.0
- symfony/string: ^5.0 || ^6.0 || ^7.0
- symfony/translation-contracts: ^2.1
Requires (Dev)
- doctrine/collections: ^1.6.8
- phpstan/phpstan: ^1.4.6
- phpunit/phpunit: ^11.5
- rector/rector: ^1.2
- slim/psr7: ^1.3
- squizlabs/php_codesniffer: 3.7.1
- symfony/cache: ^7.1
This package is auto-updated.
Last update: 2026-06-04 21:44:16 UTC
README
Implementation of JSON API Standard Specification
This project goal is to create easy-to-use library to implement JSON API specification.
Whole project is at the beginning of development. So don't hesitate to help. I'm open to some good ideas how make this more customizable and friendly.
Library only provides wrappers to create valid JSON API document. Controllers and Response is on you.
Issues
You can write email or create issue in gitlab
Installation
Install library via Composer
composer require jaspr/mapper
Basic Usage
For simplicity, we use
$containeras some dependency provider (Dependency Injection).
Describing your objects
You can choose which way you want to describe your object metadata.
With Annotations
Note: If you want to use annotations you have to use
AnnotationDriverinMetadataFactory
How you can see, setting up resource object is quiet easy. Just annotate your getter with `#[Attribute]
or #[Relationship]` annotation.
Schema
The important part is to implement Resource interface. Then fill up static method getSchema.
Note: If you want to use schema you have to use
SchemaDriverinMetadataFactory
Polymorphic relationships (abstract targets)
A relationship target may be an abstract base class, letting a single relationship resolve to any of
its concrete subtypes. Annotate the abstract base as a resource and each concrete subtype as its own resource:
#[API\Resource(type: 'payment-method')]
abstract class PaymentMethod
{
#[API\Id] public string $id;
#[API\Attribute] public string $status; // common fields live on the base
}
#[API\Resource(type: 'card-payment')]
class CardPayment extends PaymentMethod { #[API\Attribute] public string $pan; }
#[API\Resource(type: 'wire-payment')]
class WirePayment extends PaymentMethod { #[API\Attribute] public string $iban; }
class Order
{
#[API\Id] public string $id;
// target is the abstract base; the related resource may be any concrete subtype
#[API\Relationship(PaymentMethod::class)] public PaymentMethod $method;
}
Requirements and behavior:
- The abstract base must declare a common
#[Id]; any#[Attribute]/#[Relationship]declared on it are the common fields shared by all subtypes. - Concrete subtypes are discovered by inheritance, so they must live under the paths scanned by
MetadataFactory. An abstract base with no registered concrete subtype logs a warning at build time. - Encoding/decoding is polymorphic: the concrete
typeis used. Writing a relationship whosetypeis not one of the allowed concrete subtypes is rejected with a validation error. - Filtering through a polymorphic relationship is restricted to the common fields declared on the abstract base (subtype-only fields are not addressable).
- OpenAPI: the relationship
dataidentifiertypeis an enum of the concrete subtypes, and related/include schemas expand to the concrete subtypes. No standalone CRUD path is generated for the abstract base — it is a relationship target only. - TypeScript: the relationship is emitted as a union of the concrete subtype interfaces
(e.g.
ToOneRelationship<CardPayment | WirePayment>); the abstract base is not emitted as an interface and its common fields are inlined into each subtype. - Route registration:
MetadataRepository::getAll()still returns the abstract base. Consumers registering CRUD routes should skip entries whereisAbstract()istrue(abstracts are not instantiable). UsegetSubTypes(string $type)/getConcreteByClass(string $className)to resolve the concrete subtypes.
Note: interfaces as targets are not supported yet — use an abstract class.
MetadataRepository
To create MetadataRepository we must use MetadataFactory.
Usage
<?php
/** @var $container Psr\Container\ContainerInterface */
// This is cache instance implements PSR SimpleCache
$cache = $container->get( Psr\SimpleCache\CacheInterface::class);
// This is AnnotationDriver or SchemaDriver, depends on your preferences
$driver = $container->get(\JSONAPI\Driver\Driver::class);
// Paths to your object representing resources
$paths = ['paths/to/your/resources','another/path'];
// Factory returns instance of MetadataRepository
$repository = JSONAPI\Factory\MetadataFactory::create(
$paths,
$cache,
$driver
);
Encoder
Options
| Param | Default | Description |
|---|---|---|
| repository | Instance of MetadataRepository. |
Usage
<?php
// First we need DocumentBuilderFactory
// Let's get MetadataRepository from DI
/** @var $container Psr\Container\ContainerInterface */
$metadataRepository = $container->get(JSONAPI\Metadata\MetadataRepository::class);
$encoder = \JSONAPI\Mapper\Encoding\EncoderFactory::createDefaultEncoder($metadataRepository)
$data = new \JSONAPI\Mapper\Test\Resources\Valid\GettersExample('id');
/** @var \JSONAPI\Mapper\Components\Membership\ResourceObjectIdentifier $identifier */
$identifier = $encoder->identify($data);
/** @var \JSONAPI\Mapper\Components\Membership\ResourceObject $resource */
$resource = $encoder->encode($data);
/** @var \JSONAPI\Mapper\Components\Membership\Document $document */
$document = $encoder->compose($data);
Request Parser
This object works with url, and parse required keywords as described at JSON API Standard
Options
| Param | Default | Description |
|---|---|---|
| baseUrl | URL where you API lays. Must end with / to work properly with relative links. | |
| repository | Instance of MetadataRepository. | |
| pathParser | PathParser | Instance of PathParserInterface. Provides information about path, like resource type, resource ID, relation type, is it collection or is it relationship. |
| paginationParser | OffsetStrategyParser | Instance of PaginationParserInterface. Pagination. |
| sortParser | SortParser | Instance of SortParserInterface. Sort. |
| inclusionParser | InclusionParser | Instance of InclusionParserInterface. Inclusion. |
| fieldsetParser | FieldsetParser | Instance of FieldsetParserInterface. Sparse Fields |
| filterParser | ExpressionFilterParser | FilterParserInterface instance, which is responsible for parsing filter. Filter |
| bodyParser | BodyParser | Instance of BodyParserInterface. |
| logger | NullLogger | LoggerInterface instance, PSR compliant logger instance. |
Filter
As described, specification is agnostic about filter implementation. So I created, more like borrowed, expression filter from OData. So now you can use something like this:
?filter=stringProperty eq 'string' and contains(stringProperty,'asdf') and intProperty in (1,2,3) or boolProperty ne true and relation.property eq null
Or if you have simpler use cases you can try QuatrodotFilter:
?filter=stringProperty::contains::Bonus|boolProperty::eq::true
Pagination
I implement two of three pagination technics
- LimitOffsetPagination
- PagePagination
Includes
https://jsonapi.org/format/#fetching-includes
Sort
https://jsonapi.org/format/#fetching-sorting
Index Page
If you want use JASPR SDK to its full potential, consider expose index page.
<?php
$doc = new JSONAPI\Mapper\IndexDocument(self::$mr, self::$url);
$response = json_encode($doc);
which returns something like this:
{
"jsonapi": {
"version": "1.0"
},
"links": {
"relation": "https:\/\/unit.test.org\/relation",
"getter": "https:\/\/unit.test.org\/getter",
"meta": "https:\/\/unit.test.org\/meta",
"prop": "https:\/\/unit.test.org\/prop",
"third": "https:\/\/unit.test.org\/third"
},
"meta": {
"title": "JSON:API Index Page",
"baseUrl": "https:\/\/unit.test.org/"
}
}
And if your front-end use jaspr/client-js library, then you can use useJsonApiWithIndex factory to enjoy RESTful
experience.
Open API Schema
This library provides lightweight wrapper around OAS. It can generate OAS v3.0.3 schema in json, so you can provide doc for your api easily.
Basic Example
$factory = new OpenAPISpecificationBuilder(
$metadataRepository,
'https://your.api.url'
);
$info = new Info('JSON:API OAS', '1.0.0');
$info->setDescription('Test specification');
$info->setContact(
(new Contact())
->setName('Tomas Benedikt')
->setEmail('tomas.benedikt@gmail.com')
->setUrl('https://gitlab.com/jaspr')
);
$info->setLicense(
(new License('MIT'))
->setUrl('https://gitlab.com/jaspr/mapper/-/blob/5.x/LICENSE')
);
$info->setTermsOfService('https://gitlab.com/jaspr/mapper/-/blob/5.x/CONTRIBUTING.md');
$oas = $factory->create($info);
$oas->setExternalDocs(new ExternalDocumentation('https://gitlab.com/jaspr/mapper/-/wikis/home'));
$json = json_encode($oas);
For more examples, try look at tests