When performing an eloquent query on a model (using the MySQL driver) which has some numeric fields and then return a json response of the results, the json appears to pass numeric values as strings rather than numbers.
E.g.
$properties = Model::find(6);
return Response::json($properties);
Returns something like:
{
"name": "A nice item",
"value": "160806.32"
}
When it should return:
{
"name": "A nice item",
"value": 160806.32
}
In normal php you can use the JSON_NUMERIC_CHECK to solve this but there appears to be no such option for the Response::json() method. How can I ensure numeric fields are returned as numbers rather than strings?
You can actually pass that option over. If we take a look at the source code for the JsonResponse class you can pass json_encode options as the last parameter.
It would look something like this
return Response::json($properties, 200, [], JSON_NUMERIC_CHECK);
Alternatively you could do this:
return Response::make(
$properties->toJson(JSON_NUMERIC_CHECK),
200,
['Content-Type' => 'application/json']
);
Note: if $properties is not an Elequoent model then it must at least implement the JsonableInterface
as well as:
return Response::make(
json_encode($properties->toArray(), JSON_NUMERIC_CHECK),
200,
['Content-Type' => 'application/json']
);
The toJson() method in Eloquent just wraps json_encode() and passes it the array of your model. I'd recommend using one of the first two options.
Use method setEncodingOptions of JsonResponse:
return response()->json($properties)->setEncodingOptions(JSON_NUMERIC_CHECK);
Related
I have a PHP plain array which I need converted to it's original entity. Example:
class Contact
{
protected $name;
getName(){}
setName(){}
}
This gets send back and forth via an API, and at some point I have that contact as an array element:
$example = ['name'=>'Foo Bar'];
I would like that back as an Contact class. At the moment, I can do that via a serialize/deserialize, but I'm hoping there is a more efficient method for this:
foreach($examples as $example) {
$temp = $this->serializer->serialize($example, 'json');
$contact = $this->serializer->deserialize($temp, Contact::class, 'json');
}
This works, and $contact is now instance of Contact. But I have to perform this on 100 items in one go, possibly more.
I'm thinking of creating a toObject() method, which assigns the values by keys, but that doesn't seem a lot better.
Is there way to accomplish this without writing my own logic or doing the extra serializing step?
Please note: I get the data array, I cant get the 'raw' json. Please take that 'as is'.
Denormalizing from raw JSON input
If you are getting the information from an API, you could probably do away with the JSON conversion and deal with the input directly, since most likely the API is not sending you a native array, but a JSON you are converting to an array at some point
The Serializer component can handle arrays as well, directly.
Assuming an input JSON like this:
$data = '[
{
"name": "Mary"
},
{
"name": "Jane",
},
{
"name": "Alice"
}
]';
You could call deserialize() saying you expect Contact[] in your input data:
$contacts = $serializer->deserialize($data, Contact::class . '[]', 'json');
This would get you a Contact array in one single step.
Denormalizing from array to object
If for some reason the original input is not available or not readily unserializable and you really need to convert from an array to an object one by one, and your objects have setters like the ones you show in your question; you could simply use the GetSetMethodNormalizer (one of the normalizers than the Serializer component uses internally).
E.g.:
$contacts = [
['name' => 'Mary'],
['name' => 'Jane'],
['name' => 'Alice'],
];
$normalizer = new GetSetMethodNormalizer();
foreach($contacts as $arrayContact){
$contact = $normalizer->denormalize(Contact::class, $arrayContact);
// do something with $contact;
}
In my HTML frontend, I have a jQuery DataTable displaying all records fetched via AJAX from the database - a rather pretty straight forward thing. I use the Laravel Collection's ->transform(function($o){ . . . }) to iterate through the collection and return it in an array-esque manner. Just think of the following piece of code in a controller:
$cAllRecords = DatabaseRecord::all();
if(!empty($aData['sFilterIds']))
{
$cAllRecords = $cAllRecords->whereIn('creator', explode(',', $aData['sFilterIds']));
}
return response()->json(['data' => $cAllRecords->transform(function ($oDatabaseRecord) {
/** #var $oDatabaseRecord DatabaseRecord */
$sActionsHtml = 'edit';
$sUrl = route('some.route', ['iDatabaseRecordId' => $oDatabaseRecord->getAttribute('od')]);
return [
$oDatabaseRecord->getAttribute('id'),
$oDatabaseRecord->getAttribute('updated_at')->toDateTimeString(),
$oDatabaseRecord->getAttribute('created_at')->toDateTimeString(),
$sActionsHtml
];
})]);
I'm actually just filtering for records created by certain user IDs (the whereIn() call in line 4. However, the response sent back to the client looks different for different users filtered leading the jQuery table to show 'no records available', as it had received an malformed answer from the server. For one user, the response looks like this:
{
"data":[
[
1,
"2019-05-29 16:44:53",
"2019-05-29 16:44:53",
"<a href=\"#\">edit<\/a>"
]
]
}
This is a correctly formed server response and will show up in the table regularly. Great! Now something that drives me insane - the same code for another user (ID 1, while the first request was for user ID 2) returns this:
{
"data":{
"1":[
3,
"2019-05-29 17:08:49",
"2019-05-29 17:08:49",
"<a href=\"#\">edit<\/a>"
]
}
}
which, pretty obviously, is malformed and is not correctly parsed by the datatable. OK, now combing them two filters and filtering for user ID 1 and 2 will, again, return the response correctly formatted:
{
"data":[
[
1,
"2019-05-29 16:44:53",
"2019-05-29 16:44:53",
"<a href=\"#\">edit<\/a>"
],
[
3,
"2019-05-29 17:08:49",
"2019-05-29 17:08:49",
"<a href=\"#\">edit<\/a>"
]
]
}
I tried a number of things, none of which had worked since it's merely guessing why it could work with one user and not with another. (Things like reversing the order of IDs to be filtered, etc., but I found out that the filtering is not the problem. It MUST be the transform, which behaves inconsistent.)
Any ideas on why this happens and how to tackle it? I mean, it's not the only way to achieve what I'm after, I was using ->each() and array_push for all the time before but wanted to get rid of it for the sake of making use of Laravel's helpers (or possibilites) - the manual iteration and array pushing process worked out seamlessly before, and even other parts of the app work well with the Collection transform over array iteration and pushing. Why doesn't it here?
Update: The ->map() collection method behaves exactly same. Map, as opposed by transform, does not alter the collection itself. However, this should not be a relevant part within this application any way. I really can't understand what's going wrong. Is this possibly Laravel's fault?
Please note that transform method returns a Illuminate\Support\Collection.
It's better that you call all() after the transform to get an array result.
Like this:
...
return response()->json(['data' => $cAllRecords->transform(function ($oDatabaseRecord) {
/** #var $oDatabaseRecord DatabaseRecord */
$sActionsHtml = 'edit';
$sUrl = route('some.route', ['iDatabaseRecordId' => $oDatabaseRecord->getAttribute('od')]);
return [
$oDatabaseRecord->getAttribute('id'),
$oDatabaseRecord->getAttribute('updated_at')->toDateTimeString(),
$oDatabaseRecord->getAttribute('created_at')->toDateTimeString(),
$sActionsHtml
];
})->all()]);
#Cvetan Mihaylov's answer made me look at all the available collection methods (https://laravel.com/docs/5.8/collections#available-methods) and I found ->values() to return the values reindexed. And - that did the trick! :-)
return response()->json(['data' => $cAllRecords->transform(function ($oDatabaseRecord) {
/** #var $oDatabaseRecord DatabaseRecord */
$sActionsHtml = 'edit';
$sUrl = route('some.route', ['iDatabaseRecordId' => $oDatabaseRecord->getAttribute('od')]);
return [
$oDatabaseRecord->getAttribute('id'),
$oDatabaseRecord->getAttribute('updated_at')->toDateTimeString(),
$oDatabaseRecord->getAttribute('created_at')->toDateTimeString(),
$sActionsHtml
];
})->values()]);
I have a line of code similar to the following:
Sport::pluck('id', 'name)
I am dealing with frontend JavaScript that expects a list in this format:
var list = [
{ text: 'Football', value: 1 },
{ text: 'Basketball', value: 2 },
{ text: 'Volleyball', value: 3 }
...
]
I am trying to figure out how I can somehow transform the id and name values that I pluck from my model to a format similar to the Javascript list.
If that's unclear, I am looking to end up with an associative array that contains two keys: text and value, where text represents the name field on my model, and where value represents the id of the model - I hope this makes sense.
How would I approach this?
I initially tried something like this (without checking the documentation)
Sport::pluck(["id" => "value", "name" => "text]);
But that isn't how you do it, which is quite clear now. I've also tried some map-related snippet, which I cannot seem to Ctrl-z to.
Any suggestions?
Another method is to use map->only():
Sport::all()->map->only('id', 'name');
The purpose of pluck is not what you intend to do,
Please have a look at below examples,
Sport::selectRaw("id as value, name as text")->pluck("text","value");
// ['1' => 'Football', '2'=>'BasketBall','3'=>'Volleyball',...]
Syntax
$plucked = $collection->pluck('name', 'product_id');
// ['prod-100' => 'Desk', 'prod-200' => 'Chair']
Please see the documentation.
Your output is possible using simple code.
Sport::selectRaw('id as value, name as text')->get();
You could use map.(https://laravel.com/docs/5.8/collections#method-map)
$mapped = Sport::all()->map(function($item, $index) {
return [
"id" => $item["id"],
"name" => $item["text"]
];
});
This is the easiest way. Actually Laravel offers a better way for it. You can use api resources to transform your data from eloquent for the frontend:
https://laravel.com/docs/5.8/eloquent-resources
Try with toArray function:
Sport::pluck('id', 'name)->toArray();
Then you can return your result with json_encode php function;
I am absolutely new in PHP and moreover in Laravel framework (I don't know if Laravel provides some utility class for this kind of tasks). I came from Java.
So I have the following problem:
Into a class I perform a call to a REST web service, something like this:
$response = $client->get('http://localhost:8080/Extranet/login',
[
'auth' => [
'dummy#gmail.com',
'pswd'
]
]);
$dettagliLogin = json_decode($response->getBody());
\Log::info('response: '.(json_encode($dettagliLogin)));
$response->getBody() contains the returned JSON object, this is the output of the previous \Log::info():
{
"id":5,
"userName":"Dummy User",
"email":"dummy#gmail.com",
"enabled":true
}
So I have the following problems:
1) What exactly returns the json_decode() function? I really can't understand because PHP is not strongly typed and I have not a declared return type.
This is the method signature:
function json_decode($json, $assoc = false, $depth = 512, $options = 0)
and in the related doc it says #return mixed. What exactly means "mixed"?
2) Anyway the main problem is: I have to use the content of the previous returned JSON object and put these value into the related field of an array like this:
$attributes = array(
'id' => HERE THE id FIELD VALUE OF MY JSON OBJECT,
'username' => HERE THE email FIELD VALUE OF MY JSON OBJECT',
'name' => HERE THE userName FIELD VALUE OF MY JSON OBJECT,
);
So I think that I have to parse the value of the $response->getBody() or of the json_decode($response->getBody()) to obtain these values. But how exactly can I do it? What is the neater way to do it? Does the Laravel framework provide some utility to do it?
For better understanding, let's first describe - what's JSON?
It's a way of representing objects (arrays, objects, etc) in a string.
1) What exactly returns the json_decode() function? I really can't
understand because PHP is not strongly typed and I have not a declared
return type. This is the method signature:
function json_decode($json, $assoc = false, $depth = 512, $options =
0) and in the related doc it says #return mixed. What exatly means
mixed?
json_deocde converts the JSON string into the original "structure" it represent.
#return mixed means that the returned value of json_decode can be any type of variable. If the JSON represent an array - it would be an array type, if it represent an object - it would be an object type.
2) Anyway the main problem is: I have to use the content of the
previous returned JSON object and put these value into the related
field of an array like this:
$attributes = array(
'id' => HERE THE id FIELD VALUE OF MY JSON OBJECT,
'username' => HERE THE email FIELD VALUE OF MY JSON OBJECT',
'name' => HERE THE userName FIELD VALUE OF MY JSON OBJECT,
);
In order to make sure which type of variable your JSON represent, you can use var_dump(json_decode($json));. Anyway, it's a class object.
Therefore:
$object = json_decode($json);
$attributes = array(
'id' => $object->id,
'username' => $object->email,
'name' => $object->userName,
);
If you json string is an object (not an array) it will return an object (of type stdClass). Mixed means it can be multiple things, so if it was a json array, you'd get an array.
Best thing to do is use json_decode, and then var_dump (or var_export) to see what you actually get.
Using jQuery 1.7.2 and jQuery UI 1.8.18. If I use local data for the source attribute, everything works as expected. According to the documentation, a source array can be an array of string values or an array of objects:
Array: An array can be used for local data. There are two supported
formats:
An array of strings: [ "Choice1", "Choice2" ]
An array of objects with label and value properties: [ { label: "Choice1", value:
"value1" }, ... ]
Additionally, the source attribute can be a URL that responds with JSON data formatted as shown above:
String: When a string is used, the Autocomplete plugin expects that
string to point to a URL resource that will return JSON data. It can
be on the same host or on a different one (must provide JSONP). The
Autocomplete plugin does not filter the results, instead a query
string is added with a term field, which the server-side script should
use for filtering the results. For example, if the source option is
set to "http://example.com" and the user types foo, a GET request
would be made to http://example.com?term=foo. The data itself can be
in the same format as the local data described above.
If my JSON responder returns a simple array of strings, autocomplete works exactly as it should. If, however, my JSON responder returns an array of objects formatted as above, the request is made to the URL but the dropdown list is never populated. The JavaScript console shows no errors.
The autocomplete invocation looks like this:
var source_url = '/json/codes.php?type=globalcode&cid=25';
$('.gcode').autocomplete({
minLength: 2,
source: source_url
});
The responder is written in PHP. It is just a stub until I get this problem solved:
header('Content-Type: application/json, charset=UTF-8');
...
if( !$_REQUEST['type'] || !$_REQUEST['cid'] ){
echo('[]');
return false;
}
if( $_REQUEST['type'] == 'globalcode' ){
$cid = sprintf("%d", $_REQUEST['cid']);
$stub = "[ { label: 'Label for 1234', value: '1234' }, { label: 'Label for 5678', value: '5678' } ]";
echo( $stub );
return false;
}
Again, it works with both kinds of arrays when the data is local and it works with an array of string values when the data is remote. When the data is a remote array of objects, the list is never populated and JavaScript throws no errors.
You have invalid JSON, this is never logged in the console.
JSON cannot have single quotes, use double quotes, also use JSONLint to check your JSON.
This is the valid version of your JSON:
[
{
"label": "Labelfor1234",
"value": "1234"
},
{
"label": "Labelfor5678",
"value": "5678"
}
]
You could use json_encode() instead
$stub = array(
array(
"label"=>"Labelfor1234",
"value"=>"1234"
),
array(
"label"=>"Labelfor5678",
"value"=>"5678"
)
);
echo json_encode($stub);