Hello,
I'm working on application that accepts following data ( A ):
{
"data": {
"type": "resource",
"attributes": {
"class" : "single",
"data" : [
{
"key-1" : "value-1"
}
]
}
}
}
However sometimes it can be sent as following ( B ):
{
"data": {
"type": "resource",
"attributes": {
"class" : "multi",
"data" : [
{
"key-1" : "value-1"
},
{
"key-2" : "value-2"
}
]
}
}
}
In such case I need internally brake down to the following, and save it as in ( A ):
{
"data": {
"type": "resource",
"attributes": {
"class" : "single",
"data" : [
{
"key-1" : "value-1"
}
]
}
}
}
{
"data": {
"type": "resource",
"attributes": {
"class" : "single",
"data" : [
{
"key-2" : "value-2"
}
]
}
}
}
md5-0e04d25978decb596f9a1bb0bedf2094
<?php namespace App\Http\Controllers\Api\S1;
use CloudCreativity\LaravelJsonApi\Contracts\Store\StoreInterface;
use CloudCreativity\LaravelJsonApi\Http\Controllers\JsonApiController;
use CloudCreativity\LaravelJsonApi\Http\Requests\CreateResource;
use CloudCreativity\LaravelJsonApi\Utils\Str;
use Symfony\Component\HttpFoundation\Response;
class TicketsController extends JsonApiController
{
/**
* @inheritDoc
*/
public function create(StoreInterface $store, CreateResource $request)
{
if ($request->get('type') == "single") {
$record = $this->transaction(function () use ($store, $request) {
return $this->doCreate($store, $request);
});
if ($this->isResponse($record)) {
return $record;
}
return $this->reply()->created($record);
} else {
$this->transaction(function () use ($store, $request) {
foreach($request->get('data') AS $data) {
// @TODO: create as many requests as data
// @TODO: "key-1" : "value-1"
// @TODO: "key-2" : "value-2"
$this->doCreate($store, new Request(
[
"type" => "single",
"data" => $data
]
));
}
});
// @TODO: if no errors return 202?
}
}
}
One thing is not clear to me how I can alter/change CreateResource json parameters or even to create new instance of CreateResource with such new data.
Is this approach valid? Maybe there is better suggestions how to achieve the same?
Note that I have embedded loop in transaction, it is very important here, in case some one of the create will fail I need to rollback everything back.
Thanks.
The HTTP request JSON body that is returned from the CreateResource request is intentionally immutable, because the idea is to provide what the API client sent down the stack, rather than altering it.
The request data you are trying to manipulate is not JSON API compliant. As such, it should not be sent from the client with a application/vnd.api+json media type. The way to support this in your API is for it to be decoded from a different media type. To do that, see this chapter of the docs:
https://laravel-json-api.readthedocs.io/en/latest/features/media-types/#decoding
You'll need to follow the instructions on Custom Decoding, as it is not possible for this library to process what you have in your request because it is not compliant with the JSON API media type.
I'm not sure what do you mean by saying that such approach is not JSON API compliant, which JsonAPI spec part I'm violating? It is a business logic what I do with data internally, isn't it?
Thanks for the tips and answer!
Ah, no problem I can explain it a bit more...
The JSON API spec defines what the JSON content of the client's request should be. This:
{
"type" : "single",
"data" : [
{
"key-1" : "value-1"
}
]
}
Does not comply with it, because it is not a resource object as defined by the spec. For the create request, the spec says:
The request MUST include a single resource object as primary data.
https://jsonapi.org/format/#crud-creating
As such, a media type of application/vnd.api+json is not appropriate for the request as you've described it.
One thing I wanted to ask: are you not getting this response to your create request?
{
"errors": [
{
"detail": "The member data must be an object.",
"source": {
"pointer": "/data"
},
"status": "400",
"title": "Non-Compliant JSON API Document"
}
]
}
That's what you should be seeing.
Oh, I think I have misleaded you by not providing complete and valid json api snippets, my bad, sorry. Actually payloads are valid JSONAPI, I have just wrote them not complete because to save some space.
Of course I receive 400 in case of not valid payload. Please see updated issue description for valid requests.
Ah right ok, that makes a lot more sense!
You can do what you're trying to do in the adapter. That's the right place to do it, because the adapter owns all the conversion of data from the client supplied data into your domain record.
Overload the fillAttributes method:
protected function fillAttributes($record, \Illuminate\Support\Collection $attributes)
{
// do the conversion of $attributes here.
parent::fillAttributes($attributes);
}
Sorry for late reply, I used similar approach by overriding create method instead, but anyways your suggestion just confirmed that such logic should live in adapter and looks like I'm on the right track. Thanks!
Most helpful comment
Ah right ok, that makes a lot more sense!
You can do what you're trying to do in the adapter. That's the right place to do it, because the adapter owns all the conversion of data from the client supplied data into your domain record.
Overload the
fillAttributesmethod: