Allow serialization to array, not just encoded string
lindyhopchris opened this issue · comments
There are occasions when it is necessary to serialize a JSON API payload to an array, rather than the encoded string that Encoder
currently returns.
For example, in Laravel when broadcasting data via a websocket, Laravel expects the data as an array rather than a string. This is typically because third-party PHP libraries will also expect the data as an array.
https://laravel.com/docs/5.4/broadcasting#broadcast-data
My suggestion would be for there to be an interface that has a similar pattern as the EncoderInterface
, but returns arrays. E.g. if it was called SerializerInterface
then:
EncoderInterface | SerializerInterface |
---|---|
withLinks | withLinks |
withMeta | withMeta |
withJsonApiVersion | withJsonApiVersion |
withRelationshipSelfLink | withRelationshipRelatedLink |
encodeData | serializeData |
encodeIdentifiers | serializeIdentifiers |
encodeError | serializeError |
encodeErrors | serializeErrors |
encodeMeta | serializeMeta |
Then Encoder
would take a Serializer
instance in its constructor, use that to get the array that it then encodes to a string. This would allow you to create just a serializer if you didn't want to encode the JSON API document to a string. I.e. Encoder::instance()
and Serializer::instance()
would both exist.
I'm happy to submit a PR for this feature, but thought I'd run it past you first.
I actually foresaw that some would like to have raw array without converting it to json string 😄
Probably the easiest way is to change behavior of Encoder
itself.
class Encoder
{
// this is the final step in all encode methods
protected function encodeToJson(array $document)
{
return $this->encoderOptions === null ?
json_encode($document) :
json_encode($document, $this->encoderOptions->getOptions(), $this->encoderOptions->getDepth());
}
}
Option 1
class YourEncoder extends Encoder
{
// this is the final step in all encode methods
protected function encodeToJson(array $document)
{
return $document;
}
}
YourEncoder::instance(...)->...;
Pros: fast and easy, no changes in lib required. Cons: encodeToJson actually do not encode to json (probably would be better to rename it to something neutral), return type is changed from string to array.
Option 2
Wrap final step into interface or Closure.
Pros: more obvious way that Encoder may return not only strings. Cons: really not better than option 1 and requires changes in code, doesn't look nice imho.
Option 3 (this one looks nice to me)
Refactor Encoder code in a way you can extend Encoder with new methods serializeXXX
so you can have this functionality with 1-2 lines for each method. We only need to make the last step optional/configurable.
Pros: clean way to have strongly typed results, no extra instances/memory allocations. Cons: would require some coding on your side (could be mitigated by built-in trait for serializeXXX
methods).
Your thoughts?
Cool, glad you're open to adding it.
The EncoderInterface::encodeXXX
methods are (correctly) type-hinted to return strings. Having the methods return either strings or arrays is messy (imho) because you don't know for sure what you're getting if you're handling the interface (as opposed to a specific instance).
Plus encoder takes encoding options, which are not required for producing arrays, so imho it makes sense for the serializer to be a separate thing rather than something that's mixed in with encoding. (I tend to always separate concerns - here the serializer's concern is to produce an array, the encoder's concern is to encode a serialized array).
A compromise if you don't want separate units would be to have the serializeXXX
and encodeXXX
methods to be on the EncoderInterface
- i.e. so you know if you are handling the interface whether you're getting an array or string based on which method you call.
I pretty much always handle the interface - i.e. my consuming code would never know what instance it has, so I'm keen for there to be interface methods somewhere. Either EncoderInterface
with encodeXXX
and serializeXXX
, or separate EncoderInterface
or SerializerInterface
.
I almost finished the changes. The only issue is with encoding empty errors (no properties at all, just nothing). They cannot be stored as empty arrays because when converted to json they become empty arrays instead of empty objects.
Still thinking what can we do with it.
I'd filter them out: it makes no sense to have an empty error object.
That's the only idea I came up with
Due to changes it will require new version. I'm thinking of v1 finally.