Decoding a multidimensional Json array and seperate it into 3 different arrays - php

I am receiving the json payload below from a webservice and I am trying to separate it into 3 different arrays using loops after decoding whlist keeping the same array keys:
{
"id": 5705376,
"title": "Satellite Installation",
"created_at": "2017-09-07T14:02:19.000Z",
"updated_at": "2017-09-07T14:02:19.000Z",
"customer_id": 3126803,
"way_points": [{
"id": 7405587,
"lat": -26.0578251,
"lng": 28.02189520000002,
"task_id": 5705376,
"done": false,
"allow_editing_inventory": true,
"customer_contact_ids": [3126803],
"customer": {
"original_phone_number": null,
"last_open_at": null,
"last_order_at": null,
"uploaded_profile_image": {
"url": "/images/avatar.png"
},
"original_lat": null,
"original_lng": null,
"district": null,
"house_number": null,
"street": null
},
"full_address": "8 Anslow Ln, Bryanston, Sandton, 2191, South Africa"
}],
"customer": {
"id": 3126803,
"name": "Lance",
"address": "8 Anslow Ln, Bryanston, Sandton, 2191, South Africa",
"lat": -26.0578251,
"lng": 28.0218952,
"created_at": "2017-08-29T10:00:32.360Z",
"updated_at": "2017-09-07T14:02:19.860Z",
"phone": null,
"merchant_id": 11221,
"allow_login": false,
"stripe_id": null,
"original_phone_number": null,
"last_open_at": null,
"last_order_at": null,
"uploaded_profile_image": {
"url": "/images/avatar.png"
},
"original_lat": null,
"original_lng": null,
"house_number": null,
"street": null
},
"late": false,
"external_id": "5705376",
"uuid": "28095e33-b30c-4e35-98d2-aae6ad428f66",
"dispatcher_id": null,
"team_ids": [12422],
"ready_to_execute": true,
"tip_driver_enabled": false,
"automatically_assigned": false,
"task_inventories": null,
"task_notes": null
}
For the values in the "way_points" key i want to come up with one array called waypoint after decoding whilst ignoring the customers array in it. I also want to change the "id" key name to "waypoint_id" in the process.
For the values in the "customers" key (in the main array) I want to come up with one array called "waypoint" after decoding whilst assigning the value of the "url"
key to the "uploaded profile image" key. I also want to change the "id" key name to "customer_id" in the process.
Any element that is outside of the arrays mentioned above (thus elements in the main array that are not assigned to arrays), I want them to form an array called "orders".
How can i go about doing this. I am new at LOOPS

First you'll need to decode the json encoded string into a usable format, either array or an object. You can find all possible options for json_decode here.
$inputArray = json_decode($inputString, true);
Now for the loop. To keep it simple, you can initialize the three arrays you need firsthand as empty arrays. Then, in the loop, simply check for the key value and choose what array to append the data to.
$waypoints = array();
$customers = array();
$orders = array();
$count = 0;
foreach($inputArray as $key => $element) {
/* using if statements */
if($key === 'way_points') {
$waypoints[] = $element;
}
elseif($key === 'customer') {
$customers[] = $element;
}
else {
$orders[$count][$key] = $element;
}
/* using a switch/case */
switch($key) {
case 'way_points':
$waypoints[] = $element;
break;
case 'customer':
$customers[] = $element;
break;
default:
$orders[$count][$key] = $element;
break;
}
$count++;
}
At this point you should have all the data you need, but we still haven't changed the keys. You could leave it as-is, after all it is pretty self-explanatory that the id key in $waypoints reprensents the waypoint_id. However, if you do absolutely need to change the key, there's a few ways to do this. You can loop over the newly formed $waypoints array and modify the keys in this new loop.
$waypoints = change_keys($waypoints);
function change_keys($arr) {
return array_map(function($waypoint) {
return array(
'waypoint_id' => $waypoint['id'],
'lat' => $element['lat'],
/* remaining fields */
);
}, $arr);
}
Or you can cut the amount of steps and do it in the initial foreach loop
$waypoints = array();
$customers = array();
$orders = array();
$count = 0;
foreach($inputArray as $key => $element) {
if($key === 'way_points') {
$waypoints[] = array_map(function($element) {
return array(
'waypoint_id' => $element['id'],
'lat' => $element['lat'],
/* remaining fields */
);
}, $arr);
}
/* ... */
}
You can find more info on array_map here.
As an aside, as #jeroen mentionned in the comments, you can simply use json_decode and you'll be left with a usable associative array. to loop over the waypoints for example, you would simply write foreach($myArray[waypoints] as $key => $waypoint).

Related

Remove the item from array based on child value on object

I've got the following data structure:
Array -> Object -> Array -> Object -> Object
[
{
"id":6834,
"contract_id":13,
"schedule_column_values":[
{
"id":34001,
"field_value":{
"id":324241,
"value":10,
"field":{
"id":1,
"signature":"ios"
}
}
},
{
"id":34001,
"field_value":{
"id":324241,
"value":10,
"field":{
"id":1,
"signature":"android"
}
}
}
]
}
]
What I'm trying to achieve is that if a field has the signature of "android", remove its grandparent object from schedule_column_values. Basically, if a signature is "android", the final data would look like this:
[
{
"id": 6834,
"contract_id": 13,
"schedule_column_values": [
{
"id": 34001,
"field_value": {
"id": 324241,
"value": 10,
"field": {
"id": 1,
"signature": "ios"
}
}
}
]
}
]
This is just an example but the structure is always the same and we always know what signature we're looking for. It could be anything other than android but we know the string we're looking for.
I've tried a nested foreach loop and tried unset but it doesn't seem to work. The other way is I've set a NULL to object value of schedule_column_values when the signature of field is matched, but I cannot have NULL in the object.
What would be a good way to filter out this structure?
This is a perfect use case for array_filter:
$filtered_array = [];
foreach($array as $grandparent){
$filtered_schedules = array_filter(
$grandparent->schedule_column_values,
function($item){
return $item->field_value->field->signature !== 'android';
}
);
$altered_grandparent = clone $grandparent;
$altered_grandparent->schedule_column_values = $filtered_schedules;
$filtered_array[] = $altered_grandparent;
}

Array merge on basis of same keys but different keys value should add as separate array

I have data like I have pasted bellow. I want to combine on the basis of name i.e abc name template is in different language i want it in the information name, category and All its languages on the same index.
[{
"name": "abc",
"category": "new_cat",
"selectedLanguage": [{
"de": "Deutsch",
"de_status": "APPROVED"
}]
}, {
"name": "abc",
"category": "new_cat",
"selectedLanguage": [{
"en": "English",
"en_status": "APPROVED"
}]
}, ....
As mentioned above I want the result as json pasted below.
[{
"name": "abc",
"category": "new_cat",
"selectedLanguage": [{
"de": "Deutsch",
"de_status": "APPROVED"
}, {
"en": "English",
"en_status": "APPROVED"
}]
},...{
"name": "unique_temp",
"category": "TICKET_UPDATE",
"selectedLanguage": [{
"en": "English",
"en_status": "REJECTED"
}, {
"fr": "French",
"fr_status": "REJECTED"
}]
}]
I have written a code for it as
$trimArr; //this data array
$finalTempArr = array();
$finalArr = array();
foreach ($trimArr as $tr) {
$checkIfExist = $this ->in_array_r($wr['name'], $finalArr);
if($checkIfExist == false){
$finalTempArr['name'] = $tr['name'];
$finalTempArr['category'] = $tr['category'];
$finalTempArr['selectedLanguage'] = $tr['selectedLanguage'];
}else {
array_push($finalTempArr['selectedLanguage'],$tr['selectedLanguage']);
}
array_push($finalArr, $finalTempArr);
}
echo json_encode($finalArr);
function in_array_r($needle, $haystack, $strict = false) {
foreach ($haystack as $item) {
if (($strict ? $item === $needle : $item == $needle) || (is_array($item)
&& $this->in_array_r($needle, $item, $strict))) {
return true;
}
}
return false;
}
You can just use array_reduce to accomplish it:
$result = array_reduce($array, static function(array $carry, array $item) {
$key = $item['name'] . $item['category'];
if (isset($carry[$key])) {
$carry[$key]['selectedLanguage'] = array_merge($carry[$key]['selectedLanguage'], $item['selectedLanguage']);
} else {
$carry[$key] = $item;
}
return $carry;
}, []);
if you need fresh keys after reducing, then you can use array_values on the result:
$result = array_values($result);
A simple foreach loop will do.
I want to combine on the basis of name
So just use the name as the temporary key while you group and merge the data.
Code: (Demo)
$result = [];
foreach (json_decode($json, true) as $row) {
if (!isset($result[$row['name']])) {
$result[$row['name']] = $row;
} else {
$result[$row['name']]['selectedLanguage'] = array_merge($result[$row['name']]['selectedLanguage'], $row['selectedLanguage']);
}
}
var_export(array_values($result));
The more I think about, your subarray structure is probably not fit for purpose. If you are eventually going to be searching for available languages to return the language status, it will be better to set up the array structure to be a "lookup". In other words, each selectedLanguage subarray should be a flat associative array using the language as the key and the language_status as the value. This would look like "en" => "APPROVED".
Alternative, if your scripts are already relying on the en key to find the appropriate values, then keep en as the key and make a flat associative subarray from en. I can't really say with 100% confidence what you should do, but I am reasonably sure that your data structure could be improved. Once you ditch the indexed subarray structure, you then may be able to implement a recursive merge/replace technique.

Symfony 4 & Doctrine 2 serialize after removing (first) item from collection causes converting to JSON object instead of array

I'm having a lot of trouble with serializing collection from which I've removed first element.
I have CompaniesCollection entity with Many2Many relation to Company entity.
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Company")
* #Groups({"get-by-collection-owner"})
*/
private $items;
When I fetch the object with that collection, I receive items as an array of two elements I've added in the first place. I serialize it
{
"id": 19,
"name": "collection dummy name",
"items": [
{
"id": 431,
"name": "Company 1"
},
{
"id": 435,
"name": "Company 2"
}
],
"createdAt": "2019-03-11T13:55:43+01:00",
"updatedAt": "2019-03-11T15:48:57+01:00"
},
Then I remove FIRST item:
$collection->removeItem($companyToRemove);
$em = $this->getDoctrine()->getManager();
$em->persist($collection);
$em->persist($companyToRemove);
$em->flush();
$results = $companiesCollectionRepository->getCollections($companyLoader->getCurrentCompany());
Serialize the $results and get not an array of one element, but object with key of second element from the previous array of items:
{
"id": 19,
"name": "collection dummy name",
"items": {
"1": {
"id": 435,
"name": "Company 2"
}
},
"createdAt": "2019-03-11T13:55:43+01:00",
"updatedAt": "2019-03-11T15:52:48+01:00"
},
When I reload the page and fetch this object, the collection is again one element array, not an object. Apparently Doctrine doesn't get new results from database, but returns data which has already loaded in the memory.
And serializer most likely treats this "array" as an "object", because it doesn't start with 0 for a key of first array element, but with key 1.
Is there any way to make that query again, so I get freshly generated keys, or refresh these keys?
EDIT:
Actually I've finally found simple solution for this: refresh after flush
$collection->removeItem($companyToRemove);
$em = $this->getDoctrine()->getManager();
$em->persist($collection);
$em->persist($companyToRemove);
$em->flush();
$em->refresh($collection);
I've faced the same problem. And here is another solution which seems working for me.
Custom normalizer, which will be used for collections with "holes" in they keys:
namespace App\Serializer\Normalizer;
use Doctrine\Common\Collections\Collection;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\SerializerAwareInterface;
use Symfony\Component\Serializer\SerializerAwareTrait;
class DoctrineCollectionNormalizer implements NormalizerInterface, SerializerAwareInterface
{
use SerializerAwareTrait;
public function normalize($object, $format = null, array $context = array()): array
{
/* #var $object Collection */
$values = $object->getValues();
$object->clear();
foreach ($values as $k => $value) {
$object->set($k, $value);
}
return $this->serializer->normalize($object, $format, $context);
}
public function supportsNormalization($data, $format = null): bool
{
if ($data instanceof Collection) {
$keys = $data->getKeys();
$count = count($keys);
$lastKey = (int) array_pop($keys);
return $count && $lastKey !== $count-1;
}
return false;
}
}

Laravel sort object by Key

I have the following which I would like to order alphabetically by the Key i.e first for each array group would be "bname", followed by "created_at".
{
"leads": [
{
"lead_id": 1,
"zoho_lead": null,
"bname": "ABC Limited",
"tname": "ABC",
"source_id": 11,
"industry_id": 1,
"user_id": 1,
"created_at": "2017-09-06 15:54:21",
"updated_at": "2017-09-06 15:54:21",
"user": "Sean McCabe",
"source": "Unknown",
"industry": "None"
},
{
"lead_id": 2,
"zoho_lead": 51186111981,
"bname": "Business Name Limited",
"tname": "Trading Name",
"source_id": 11,
"industry_id": 1,
"user_id": 1,
"created_at": "2017-06-01 12:34:56",
"updated_at": null,
"user": "John Doe",
"source": "Unknown",
"industry": "None"
}
]
}
I'm trying to use ksort like so in the foreach loop:
class LeadController extends Controller
{
use Helpers;
public function index(Lead $leads)
{
$leads = $leads->all();
foreach($leads as $key => $lead){
$lead->user = User::where('id', $lead->user_id)->first()->name;
$lead->source = Source::where('id', $lead->source_id)->first()->name;
$lead->industry = Industry::where('id', $lead->industry_id)->first()->name;
$lead->ksort();
}
return $leads;
}
But I get the following error:
Call to undefined method Illuminate\\Database\\Query\\Builder::ksort()
How do I use this function, or is there a Laravel way of doing this, or a better way altogether?
Thanks.
Managed to get it to return with the Keys in alphabetical order, so below is the solution in-case someone else should require it:
public function index(Lead $leads)
{
$leadOut = Array();
$leads = $leads->all();
foreach($leads as $key => $lead){
$lead->user = User::where('id', $lead->user_id)->first()->name;
$lead->source = Source::where('id', $lead->source_id)->first()->name;
$lead->industry = Industry::where('id', $lead->industry_id)->first()->name;
//Convert to Array
$leadOrder = $lead->toArray();
//Sort as desired
ksort($leadOrder);
//Add to array
$leadOut[] = $leadOrder;
}
return $leadOut;
}
There is likely a cleaner way to do this, but it works for my instance, and perhaps additional answers may be posted that are better.
You could do something like:
return Lead::with('user', 'source', 'industry')->get()->map(function ($lead) {
$item = $lead->toArray();
$item['user'] = $lead->user->name;
$item['source'] = $lead->source->name;
$item['industry'] = $lead->industry->name;
ksort($item);
return $item;
});
This should be much more efficient as it will eager load the relationships rather than make 3 extra queries for each iteration.

PHP - recursively iterate over json objects

I need to iterate over objects in PHP and to apply a certain function on each and every single value in this object.
The objects are absolutely arbitrary. They can include vars, another objects, arrays, arrays of objects and so on...
Is there a generic method to do so? If yes, how?
Usage example:
RESTful API which receives requests in JSON format.
json_decode() is executed on request body and creates an arbitrary object.
Now, it is good, for example, to execute mysqli_real_escape_string() on every value in this object before further validations.
OBJECT EXAMPLE:
{
"_id": "551a78c500eed4fa853870fc",
"index": 0,
"guid": "f35a0b22-05b3-4f07-a3b5-1a319a663200",
"isActive": false,
"balance": "$3,312.76",
"age": 33,
"name": "Wolf Oconnor",
"gender": "male",
"company": "CHORIZON",
"email": "wolfoconnor#chorizon.com",
"phone": "+1 (958) 479-2837",
"address": "696 Moore Street, Coaldale, Kansas, 9597",
"registered": "2015-01-20T03:39:28 -02:00",
"latitude": 15.764928,
"longitude": -125.084813,
"tags": [
"id",
"nulla",
"tempor",
"do",
"nulla",
"laboris",
"consequat"
],
"friends": [
{
"id": 0,
"name": "Casey Dominguez"
},
{
"id": 1,
"name": "Morton Rich"
},
{
"id": 2,
"name": "Marla Parsons"
}
],
"greeting": "Hello, Wolf Oconnor! You have 3 unread messages."
}
If you just need to walk over the data and won't need to re-encode it, json_decode()'s second parameter, $assoc will cause it to return an associative array. From there, array_walk_recursive() should work well for what you're after.
$data = json_decode($source_object);
$success = array_walk_recursive($data, "my_validate");
function my_validate($value, $key){
//Do validation.
}
function RecursiveStuff($value, $callable)
{
if (is_array($value) || is_object($value))
{
foreach (&$prop in $value) {
$prop = RecursiveStuff($prop);
}
}
else {
$value = call_user_func($callable, $value);
}
return $value;
}
And use it like:
$decodedObject = RecursiveStuff($decodedObject, function($value)
{
return escapesomething($value); // do something with value here
});
You can just pass function name like:
$decodedObject = RecursiveStuff($decodedObject, 'mysqli_real_escape_string');

Categories