-
Notifications
You must be signed in to change notification settings - Fork 48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unable to JSON encode payload. Error code: 5 #55
Comments
I may have narrowed this down to a smaller issue. A model with a geospatial attribute passed to a job causes the https://laravel.com/docs/9.x/queues#handle-method-dependency-injection
|
@devinfd Hi Devin, I tried reproducing the issue you mentioned but didn't find anything. Anyway, if you'll find something, please fork this repo and add a failing test so it will be easy to me to play with it and fix it. |
Yes and no. Laravel jobs (that use This issue was first described in 2015: laravel/framework#11100 This issue was solved in grimzy/laravel-mysql-spatial by overriding setRawAttributes https://github.com/grimzy/laravel-mysql-spatial/blob/2ca9f2f25cf10e3b4771881108ecc9c69317bf57/src/Eloquent/SpatialTrait.php#L102. Just as a test, if I make the below change in my model then everything works. Model custom casts are a nice but also flawed. /**
* The list of attributes to cast.
*
* @var array
*/
protected $casts = [
//'coordinates' => Point::class
];
public function setRawAttributes(array $attributes, $sync = false)
{
$spatial_fields = ['coordinates'];
foreach ($attributes as $attribute => &$value) {
if (in_array($attribute, $spatial_fields) && is_string($value) && strlen($value) >= 13) {
$value = \MatanYadaev\EloquentSpatial\Objects\Point::fromWkb($value);
}
}
return parent::setRawAttributes($attributes, $sync);
} |
@MatanYadaev I would appreciate a response to the above comments. |
@devinfd I agree Devin, custom casts are nice, but not perfect, unfortunately.
|
I found a solution. The miscellaneous object with a model property needs to use the |
I see, you can simplify the test to this code: $testPlace = TestPlace::factory()->create([
'point' => new Point(0, 180),
])->fresh();
$json = json_encode([
'serialized_test_place' => serialize($testPlace),
]);
expect($json)->toBeFalse();
expect(json_last_error())->toBe(5); As you already noticed, fixing this issue will require migrating from custom casts to Thanks for bringing this to my attention. |
About the comment in the docs - can you please explain what would you expect to be written in the docs? |
I don't think think this is a
I found this random tweet that was helpful: https://twitter.com/realstevebauman/status/1517522880002772993 |
@devinfd Thank you for this workaround. Making a Trait to include this instead the custom cast worked like a charm. FYI: Including Not having the time to make a PR, i do hope @MatanYadaev or anyone else will have the time to fix this.
|
Hitting the same error :(. Using setRawAttributes solves this issue but then I get other errors when I use whereDistanceSphere function. |
@localgituser Can you please fork this repo and push a commit with a failing test? I need a way to reproduce this issue. |
FWIW - I recently came across the same problem, but due to other requirements the above solutions didn't quite work for me. I still wanted to use the casts and all the other benefits this package comes with. Not sure if this will work in all situations, but works well for me. In the end I found this approach to do the trick, it may be something that can be implemented, or help others if they get stuck. I've kept it simple for this example, but nothing to stop it being abstracted away, like the static retrieved/saving/etc hooks on the model or higher. In short, this makes sure the "original" gets the same query expression as the binary "attribute", which means it can then be encoded properly in jobs/queues. I also found that when running an artisan command, and other situations where the property has not been called on before you go to save, attach to a pivot table, or whatever else you are doing, you need to call the property with the cast on it first to get it to apply. // Not *always* required, but safe if you forget. The cast needs to be invoked at some point.
$yourModel->coordinates = $yourModel->coordinates;
// Replaces the original binary (the part causing all the headaches) with the query expression.
$yourModel->syncOriginalAttribute('coordinates');
$yourModel->save();
// or...
$yourModel->places()->attach($placeId);
// or dispatching jobs, etc |
@MatanYadaev A way to reproduce the issue: The problem is, the Laravel Queue uses the scheme above to JSON encode object payloads (see https://github.com/laravel/framework/blob/10.x/src/Illuminate/Queue/Queue.php @ lines 106-->128-->157). Playing of of @devinfd s suggestion combined with @Samuel-Webster s one could try to 'abuse' the casting in setRawAttributes without any problems which would occur while trying to save the model. I would propose this addition to the HasSpatial trait: namespace MatanYadaev\EloquentSpatial\Traits;
use MatanYadaev\EloquentSpatial\Objects\Geometry;
trait HasSpatial {
// ...
public function setRawAttributes(array $attributes, $sync = false)
{
$result = parent::setRawAttributes($attributes, $sync);
foreach ($attributes as $attribute => $value) {
if ($value && is_string($value) && ! preg_match('//u', $value)) { // the string is binary
// access the attribute to force conversion via attribute cast
$spatialAttribute = $this->$attribute;
// override attribute and original attribute to get rid of binary strings
// (Those would lead to errors while JSON encoding a serialized version of the model.)
if ($spatialAttribute instanceof Geometry) {
$this->attributes[$attribute] = $spatialAttribute;
$this->original[$attribute] = $spatialAttribute;
}
}
}
return $result;
}
} |
Hi @d0m4te, can you please submit a PR with tests? |
The issue is still present after the revert of the PR #100 in V3.2.2. |
Following on from @MatanYadaev's test code
I was able to unset the public function __sleep()
{
unset($this->original['point'], $this->attributes['point'], $this->changes['point']);
return parent::__sleep();
} This seems to work well, but if you are relying on the spatial field in the job it won't be there. |
Well, actually... if we cast the binary value back to the spatial equivalent the serialization happens nicely. public function __sleep()
{
unset($this->original['point'], $this->changes['point']);
$this->attributesToArray();
return parent::__sleep();
} Updated Test $testPlace = TestPlace::factory()->create([
'point' => new Point(0, 180),
])->fresh();
$json = json_encode([
'serialized_test_place' => serialize($testPlace),
]);
expect($json)->toBeString();
expect(json_last_error())->toBe(0);
$point = unserialize(json_decode($json, true)['serialized_test_place'])->point;
expect($point)->toBeInstanceOf(Point::class);
expect($point->latitude)->toEqual(0);
expect($point->longitude)->toEqual(180); I don't need the original nor the changes in my code, but I assume they could be cast as well if needed. |
@patrickomeara do you use the trait |
@MatanYadaev The job was from a third party package but I have extended it and now use |
Thank you for your work on this package! I've been fighting an issue for the past week that I can't find a resolution to. I don't know if this is an issue with this package or Laravel but I thought that I would start here.
I have a queued job that has an object (not a model) as constructor argument. The object has property that is a model with a coordinates (Point) attribute.
something like:
The coordinates attribute is properly casted on the Account model
The problem is that the coordinates are not being serialized by laravel and I get the error:
Illuminate\ Queue\ InvalidPayloadException Unable to JSON encode payload. Error code: 5
Any ideas? I'm starting to lose my mind on this.
PHP 8.1.10
Laravel 9.28.0
The text was updated successfully, but these errors were encountered: