Create nested objects from CSV - php

I have a CSV file which needs to be processed into objects.
I can open the CSV file and get all the contents that I want, no problem there. I need to match the contents of the CSV file by headers into objects. For example:
Name | Address.Street | Address.Country | Notes.Example.Value
Object->Name
Object->Address
Object->Notes
etc.
How would I handle this dynamically, not knowing what the headers are going to be beforehand?
Essentially I want to turn a string, like "Prop.Prop.Prop.etc" into a nested object.
$headers = array(); // First row of CSV.
$row = array(); // Current row of CSV.
$record = new StdClass();
foreach ($row as $key => $value) {
$properties = explode('.', $headers[$key]);
if (count($properties > 1)) {
// ???
}
else {
$record->{$properties[0]} = $value;
}
}

This should be done through a recursion. If the property you're parsing has only one level of depth, then you set the object key as a value. (which you're already doing)
If it has two or more levels, you shift the first element of the property array and recurse on the remaining levels.
Elaborating in your example:
<?php
$headers=[
'Name',
'Email',
'Address.Street',
'Address.Country',
'Notes.Example.Value'
];
$row=[
'john',
'john#gmail.com',
'beale street',
'US',
'180'
];
function setObject(&$object, $properties, $value) {
$name=array_shift($properties);
if(count($properties)===0) {
return $object->{$name} = $value;
} else {
// if this property isn't set, we declare it as a new object
if(!isset($object->{$name}) || !is_object($object->{$name})) $object->{$name} = new StdClass();
return setObject($object->{$name}, $properties,$value);
}
}
$record = new StdClass();
foreach($row as $key=>$value) {
$properties = explode('.', $headers[$key]);
setObject($record, $properties, $value);
}
echo '<pre>';
print_r($record);
echo '</pre>';
This is probably not the most elegant solution. With a bit of work you could avoid passing the object by reference back and forth.

Related

Cross compare two sets of data

I have a report table. A report can have many documents (which are csv file paths). A document has many mappings.
So I initially loop my documents for a report and load the csv data using ParseCsv
foreach ($this->report->uploadedDocuments->documents as $document) {
$csv = new ParseCsv\Csv();
$csv->limit = 1;
$csv->parse(storage_path("app/" . $document->fullName));
$this->csvData = $csv->data;
}
If I output this, I see something like the following for each document (only showing one CSV row here from one document)
[
{
"id":"1",
"field1":"Some data",
"something":"Some data",
"something":"Some data",
"something":"Some data",
"something":"Some data",
}
]
Now each document has mappings so I can also do something like this
foreach ($this->report->uploadedDocuments->documents as $document) {
//Load CSV data
foreach ($document->mappings as $column) {
Log::debug(json_encode($column));
}
}
The output for the above is something like this
{
"document_id":"6434fc74-18f7-43ff-be33-3a8b0cf1cadc",
"field1":1,
"field2":null,
"field3":null,
"created_at":"2020-10-26T10:19:36.000000Z",
"updated_at":"2020-10-26T10:19:36.000000Z"
}
So from the above, I can see that field1 is located in column (index) 1 in the csv data. I can also see that this CSV does not have a field2 or field3. So my idea is to turn the original CSV data into this
[
{
"field1":"Some data",
}
]
So this is my thinking. I first need to loop the documents for the reports, load the CSV files, and read the data
foreach ($this->analysis->uploadedDocuments->documents as $document) {
$csv = new ParseCsv\Csv();
$csv->limit = 5;
$csv->parse(storage_path("app/" . $document->fullName));
if (empty($csv->data)) {
throw new Exception('Not data found in file.');
}
foreach ($csv->data as $dataKey => &$item) {
foreach ($item as $key => $value) {
//Now I have access to the keys and values from csv
}
}
}
But then how can I cross check this against my document mappings? I obviously have to loop the mappings but where should I do this? I have something which I think could work but it is very messy, and relies on my knowing the column names e.g.
foreach ($this->report->uploadedDocuments->documents as $document) {
$csv = new ParseCsv\Csv();
$csv->limit = 5;
$csv->parse(storage_path("app/" . $document->fullName));
if (empty($csv->data)) {
throw new Exception('Not data found in file.');
}
foreach ($document->mappings as $column) {
foreach ($csv->data as $dataKey => &$item) {
foreach ($item as $key => $value) {
//match the mapping column to the csv column
if ($column->fieldOne !== NULL && $key === $column->fieldOne) {
//now I have access to the values for that column
}
}
}
}
}
Would there be a better way of achieving something like this?
Thanks
I suggest the following as I understand from your question
<?PHP
$csvData = $csv->data;
$csvData = json_encode($csvData);
$arr = json_decode($csvData, true);
$field1 = array_column($arr, "field1");
print_r($field1);
?>
This line convert object into json string
$csvData = json_encode($csvData);
This line convert json string to pure array
$arr = json_decode($csvData, true);
This line extract column value from multidimensional array
$field1 = array_column($arr, "field1");
I hope this will work.

Retrieve all values in my loop and format it to json

I'm trying to build a page which queries my database and then formats output so another webservice/page can access the data.
Ideally I wanted to explore having the data in JSON format, but that is not working. The other problem I have which is more major than the JSON not working is, if I have 3 records in $reportsResult, only the last one is displayed.
Anyone with some help please. Oh do I also need to print_r for the external webpage to retrieve the data or is there a better way?
class Pupil {
public $FirstName = "";
public $LastName = "";
}
foreach($reportsResult->getRecords() as $reportRecord) {
$Pupil = new Pupil();
$Pupil->FirstName = $reportRecord->getField('FName');
$Pupil->LastName = $reportRecord->getField('SName');
}
json_encode($Pupil);
OK managed to figure out how how to get all records from the loop, but its still not displaying in json format when I do a print_r - am I missing something?
$AllPupils = array();
foreach($reportsResult->getRecords() as $reportRecord)
{
$Pupil = new Pupil();
$Pupil->FamID = $reportRecord->getField('FName');
$Pupil->ChildName = $reportRecord->getField('SName');
array_push($AllPupils, $Pupil);
}
json_encode($AllPupils);
Everytime your foreach loop starts again, it will override your $Pupil variable.
Try an array instead:
$Pupil = array()
$i = 0;
foreach($reportsResult->getRecords() as $reportRecord) {
$Pupil[$i] = new Pupil();
$Pupil[$i]->FirstName = $reportRecord->getField('FName');
$Pupil[$i]->LastName = $reportRecord->getField('SName');
$i++;
}
echo json_encode($Pupil);
Edit: mikemackintosh's solution should also work and could be a little bit faster (depending on the size of your foreach loop).
To display the results you need to echo your data (not only json_encode).
You will probably run into issues since json_encode wont handle the whole object. for that, you may want to serialize the $Pupil object.
Something like below may work for you though. It will assign the values to a returned array, which will allow json_encode to execute gracefully:
class Pupil {
public $FirstName = "";
public $LastName = "";
public function getAttr(){
return array("FirstName" => $this->FirstName, "LastName" => $this->LastName);
}
}
$json = array();
foreach($reportsResult->getRecords() as $reportRecord) {
$Pupil = new Pupil();
$Pupil->FirstName = $reportRecord->getField('FName');
$Pupil->LastName = $reportRecord->getField('SName');
$json[] = $Pupil->getAttr();
}
echo json_encode($json);
I am not sure why you have that class defined, but you know what in your for each have something like
foreach ($reportsResult->getRecords() as $key => $record) {
$data[$key]['firstname'] = $record->getField('Fname');
$data[$key]['lastname'] = $record->getField('Sname');
}
And then you can check the final array using print_r
and while output you can simply do a print json_encode($data) and it will give you a json string of all the items in the data array.
In php (at least), json_encode takes an array as parameter.
Therefore you should add a constructor to your class
function __construct($first, $last)
{
this.$FirstName = $first;
this.$LastName = $last;
}
and one for getting the full name as an array, ready to be jsoned
function getNameArray()
{
$nameArray = array();
$nameArray['firstName'] = this.$FirstName;
$nameArray['lastName'] = this.$LastName;
return $nameArray;
}
then in that foreach you build another array with all the pupils
$pupils = array();
foreach (bla bla)
{
$first = $reportRecord->getField('FName');
$last = $reportRecord->getField('SName');
$Pupil = new Pupil($first, $last);
array_push($pupils, $pupil.getNameArray());
}
finally, you have everything preped up
json_encode($pupils);
I'm sure there's other ways to debug your stuff, I use print_r mainly also.

Php pushing values to a 2-dimensional array

i've a 2-dimensional array and i want to push values to it with a while loop like;
$arr[0][1] = 1. value
$arr[0][2] = 2. value
i ve tried
while($zRow = mysql_fetch_array($zQuery))
{
$props[]['name'] =$zRow['name'];
$props[]['photo'] =$zRow['thumbnail'];
}
this loop pushes name to $props[0][name] and thumbnail to $props[1][photo]
i also tried
$j = 0;
while($zRow = mysql_fetch_array($zQuery))
{
$props[$j]['name'] =$zRow['name'];
$props[$j]['photo'] =$zRow['thumbnail'];
$j+=1;
}
that works but with this i when i use foreach loop later, it makes trouble like "Illegal offset type"
and here is my foreach loop
foreach($props as $no)
{
echo $props[$no]['name'];
}
now my questions;
1) are there any other way than while loop with $j variable like array_push for 2-dimensional arrays
2)how can i use foreach loop for 2-dimensional arrays
You could change the first loop to the following:
while($zRow = mysql_fetch_array($zQuery))
{
$row = array();
$row['name'] = $zRow['name'];
$row['photo'] = $zRow['thumbnail'];
$props[] = $row;
}
Your method also works, but you need that extra variable.
In your second loop, what you actually need to be doing is:
foreach($props as $index => $array)
{
echo $props[$index]['name'];
// OR
echo $array['name'];
}
Pushing anything onto an array with $myArray[] = 'foo' will increment the array's counter.
For multidimensional array, you need to populate the "inner" array, then push it to the "outer" (in your case $props) array.
while($zRow = mysql_fetch_array($zQuery)) {
$data = array('name' => $zRow['name'], 'photo' => $zRow['thumbnail']);
$props[] = $data;
}
To iterate over multidimensional arrays whose depth is known:
foreach ($props as $prop) {
foreach ($prop as $key => $value) {
echo "{$key} => {$value}" . PHP_EOL;
}
}
If the depth of the nesting is not known, you may have to use a recursive function to gather the data.

Converting array into multidimensional array in PHP when result is send from Oracle

Here is example how my array should look like:
$library = array(
'book' => array(
array(
'authorFirst' => 'Mark',
'authorLast' => 'Twain',
'title' => 'The Innocents Abroad'
),
array(
'authorFirst' => 'Charles',
'authorLast' => 'Dickens',
'title' => 'Oliver Twist'
)
)
);
When I get results from oracle database:
$row = oci_fetch_array($refcur, OCI_ASSOC+OCI_RETURN_NULLS);
But when I execute my code I only get one row.
For example: <books><book></book><name></name></books>
But I want all rows to be shown in xml.
EDIT:
This is my class for converting array to xml:
public static function toXml($data, $rootNodeName = 'data', &$xml=null)
{
// turn off compatibility mode as simple xml throws a wobbly if you don't.
if (ini_get('zend.ze1_compatibility_mode') == 1)
{
ini_set ('zend.ze1_compatibility_mode', 0);
}
if (is_null($xml))
{
$xml = simplexml_load_string("<".key($data)."/>");
}
// loop through the data passed in.
foreach($data as $key => $value)
{
// if numeric key, assume array of rootNodeName elements
if (is_numeric($key))
{
$key = $rootNodeName;
}
// delete any char not allowed in XML element names
$key = preg_replace('/[^a-z0-9\-\_\.\:]/i', '', $key);
// if there is another array found recrusively call this function
if (is_array($value))
{
// create a new node unless this is an array of elements
$node = ArrayToXML::isAssoc($value) ? $xml->addChild($key) : $xml;
// recrusive call - pass $key as the new rootNodeName
ArrayToXML::toXml($value, $key, $node);
}
else
{
// add single node.
$value = htmlentities($value);
$xml->addChild($key,$value);
}
}
// pass back as string. or simple xml object if you want!
return $xml->asXML();
}
// determine if a variable is an associative array
public static function isAssoc( $array ) {
return (is_array($array) && 0 !== count(array_diff_key($array, array_keys(array_keys($array)))));
}
}
?>
Now with below responde I have tried problem is I get following output: <book>...</book> tags after each row.. then I tried 3 dimensional array now I get: <book><book>...</book></book> on the proper place but I have 2 of them.
This is the line where I have determine which is root on that array and that's why I get this output. But don't know how to change it : $xml = simplexml_load_string("<".key($data)."/>");
Thank you.
oci_fetch_array() will always return a single row, you need to call it until there are no more rows to fetch in order to get all of them:
while ($row = oci_fetch_array($refcur, OCI_ASSOC+OCI_RETURN_NULLS))
{
$library['book'][] = $row;
}

building an associative array

This is going to be my first time building an associative array. And if anyone can help me I would be grateful.
Basically, I want to loop through a directory of XML files. I want to find out if a certain editor was the editor of this file, and if the query is true, I would like to grab two pieces of information and achieve the result of an associate array with those two pieces of information for every case where the editor's name is found.
So here's what I have got so far:
function getTitleandID($editorName) {
$listofTitlesandIDs = array();
$filename = readDirectory('../editedtranscriptions');
foreach($filename as $file)
{
$xmldoc = simplexml_load_file("../editedtranscriptions/$file");
$xmldoc->registerXPathNamespace("tei", "http://www.tei-c.org/ns/1.0");
if ($editorName == $xmldoc->xpath("//tei:editor[#role='PeerReviewEditor']/text()"))
{
$title = $xmldoc->xpath("//tei:teiHeader/tei:title[1]");
$id = $xmldoc->xpath("//tei:text/tei:body/tei:div/#xml:id[1]");
$listofTitlesandIDs[] = //I don't know what to do here
}
else
{
$listofTitlesandIDs = null;
}
}
return $listofTitlesandIDs
}
This is about where I get stuck. I'd like to be able have $listofTitlesandIDs as an associative array where I could call up the values for two different keys, e.g. $listofTitlesandIDs['title'] and $listofTitlesandIDs[$id]
So that's about it. I'm grateful for any help you have time to provide.
Well I'm sure this is a little clumsy (the result of an amateur) but it has given me the result I want.
function getTitlesandIDs($EditorName) //gets titles and IDs for given author
{
$list = array();
$filename = readDirectory('../editedtranscriptions');
foreach($filename as $file)
{
$xmldoc = simplexml_load_file("../editedtranscriptions/$file");
$xmldoc->registerXPathNamespace("tei", "http://www.tei-c.org/ns/1.0");
$title = $xmldoc->xpath("//tei:teiHeader/tei:fileDesc/tei:titleStmt/tei:title[1]");
$id = $xmldoc->xpath("//tei:text/tei:body/tei:div/#xml:id");
$editorName = $xmldoc->xpath("//tei:editor[#role='PeerReviewEditor']/text()")
if ($editorName[0] == "$EditorName")
{
$result = array("title"=>$title[0], "id"=>$id[0]);
$list[] = $result;
}
}
return $list;
}
With this I can call the function $list = getTitlesandIDs('John Doe') and then access both the title and id in the associative array for each instance. Like so:
foreach ($list as $instance)
{
echo $instance['title'];
echo $instance['id'];
}
Maybe that will help somebody some day -- let me know if you have any advice on making this more elegant.
$listofTitlesandIDs[$id] = $title;
You should loop over the array then using the foreach loop.

Categories