How can an associative multidimensional (multiple dimensions) PHP array get converted to downloadable CSV?
Asked Answered
D

3

0

I have an associative multidimensional (dynamic length of dimensions) array. It originally comes from JSON data, but I understand that just makes things harder so I convert it using json_decode($original_data, true).

I'm interested to convert it to a clickable CSV file like echo '<a href="data:application/csv, ' . $data . '">Click to download</a>'.

I've tried many code variations, one of which I found online in https://coderwall.com/p/zvzwwa/array-to-comma-separated-string-in-php because its whole purpose is to "convert a multi-dimensional, associative array to CSV data". Alas, its code doesn't seem to be recursive. Unlike other functions I've tried it doesn't call itself recursively if the data isn't is_array.

Your assistance is appreciated.

Sample data:

$array = array(
    'name' => 'Test',
    'average' => 1,
    'fp' => '',
    'dates' => array(
        'isScheduled' => '',
        'startDate' => 1587418137,
        'endDate' => 1587418137,
        'pViewValue' => array(
                    'startDate' => '2020-04-20T18:28:57.000Z',
                    'endDate' => '2020-04-20T18:28:57.000Z',
        )
    )
);
echo '<pre>' . print_r($array, true) . '</pre>';

Array
(
    [name] => Test
    [average] => 1
    [fp] => 
    [dates] => Array
        (
            [isScheduled] => 
            [startDate] => 1587418137
            [endDate] => 1587418137
            [pViewValue] => Array
                (
                    [startDate] => 2020-04-20T18:28:57.000Z
                    [endDate] => 2020-04-20T18:28:57.000Z
                )

        )
)

Expected output:

name    average fp  dates-isScheduled   date-StartDate  date-endDate    date-pViewValue-startDate   date-pViewValue-endDate
test    1                               1587418137      1587418137      2020-04-20T18:28:57.000Z    2020-04-20T18:28:57.000Z
Disvalue answered 26/4, 2020 at 16:51 Comment(0)
D
0

@nelsonrakson's answer led me to what was a very specific answer to a very general question.

Also, that answer didn't quote the header line, used blank columns for every parent title, used strings instead of arrays and thus was left with extra commas plus unneeded calls (like $csv_data .= "" . ",";), so here it is with arrays, more proper support for parent titles and also what I wanted in the first place - an auto download of the CSV which was converted from an array:

Update 1: also added support for empty arrays like [something] => array(). Update 2: also added support for Nulls, Boolean values and linebreaks

<?php
    $array = array(
        'name of' => 'Test',
        'average' => 1,
        'fp' => '',
        'dates' => array(
            'isScheduled' => '',
            'startDate' => 1587418137,
            'endDate' => 1587418137,
            'pViewValue' => array(
                        'startDate' => '2020-04-20T18:28:57.000Z',
                        'endDate' => '2020-04-20T18:28:57.000Z',
            )
        )
    );
    echo '<pre>' . print_r($array, true) . '</pre>';

    $csv_title = array();
    $csv_data = array();
    array2csv($array, $csv_title, $csv_data);
    $csv_title = implode(",", $csv_title);
    $csv_data = implode(",", $csv_data);

    echo $csv_title . "\n<br />" . $csv_data . "\n\n" . '
    <script>
    thevalue = \'' . $csv_title . "\\n\\\n" . $csv_data . '\'
    var blob = new Blob([thevalue], {type: \'attachment/csv\'});
    var blobUrl = URL.createObjectURL(blob);
    var a = document.createElement("a");
    with (a) {
        // href="data:attachment/" + extension + ";charset=utf-8;base64," + utf8_to_b64(thevalue);
        // href="data:text/" + save as + ";charset=\'utf-8\'," + thevalue;
        href=blobUrl
        download = "export.csv";
    }
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    </script>
    ';

function str_wrap($string = '', $char = '"') {
    return str_pad($string, strlen($string) + 2, $char, STR_PAD_BOTH);
}

function array2csv($array, &$csv_title, &$csv_data, $prefix = '') {
    foreach($array as $key => $value) {      
        if (!empty($prefix) || strlen($prefix) > 0)
            $key = $prefix . ' - ' . $key;
        if (is_array($value) && !empty($value))
            array2csv($value, $csv_title, $csv_data, $key);
        else {
            $csv_title[] = $key; //str_wrap($key);
            if (is_null($value))
                $csv_data[] = 'NULL';
            else if ($value === false)
                $csv_data[] = 0;
            else
                $csv_data[] = str_replace("\n", "\\n", $value);
                // $csv_data[] = str_wrap(addcslashes(str_replace("\n", "\\n", $value, ','));
        }
    }
}
?>
Disvalue answered 26/4, 2020 at 20:11 Comment(2)
Also added support for empty arrays like [something] => array().Disvalue
Also added support for Nulls, Boolean values and linebreaks.Disvalue
D
1
fputcsv()

PHP Manual

Resolved on Stackoverflow

Dupre answered 26/4, 2020 at 17:9 Comment(2)
Thanks, I used your link as a reference in my answer.Disvalue
@Disvalue My pleasure!Dupre
P
0

CSV is table-like: columns in rows, seperated by a seperator.

They are unsuitable to use for dynamic length and depth datastructures.

So the short answer is: No, don't.

However, if you happen to have some knowledge about WHAT to expect, you could predefine the 'meaning' of each column and map that to certain places inside your CSV structure. But that is trickery, and only works if you know what goes where.

Primalia answered 26/4, 2020 at 17:6 Comment(3)
How about doing the output I've added to the question? That is, adding a prefix based on the key of the upper array?Disvalue
@Disvalue sure that works. But if it is 5 levels deep, you add all the prefixes? Can be done, but everything in me shouts: Don't use CSV for this. It just isn't suited. JSON encoding sounds a lot more viable.Primalia
Yes, I add all 5. And JSON is not for end users. CSV is. All of this was meant for end users who need to generate a file to download.Disvalue
D
0

@nelsonrakson's answer led me to what was a very specific answer to a very general question.

Also, that answer didn't quote the header line, used blank columns for every parent title, used strings instead of arrays and thus was left with extra commas plus unneeded calls (like $csv_data .= "" . ",";), so here it is with arrays, more proper support for parent titles and also what I wanted in the first place - an auto download of the CSV which was converted from an array:

Update 1: also added support for empty arrays like [something] => array(). Update 2: also added support for Nulls, Boolean values and linebreaks

<?php
    $array = array(
        'name of' => 'Test',
        'average' => 1,
        'fp' => '',
        'dates' => array(
            'isScheduled' => '',
            'startDate' => 1587418137,
            'endDate' => 1587418137,
            'pViewValue' => array(
                        'startDate' => '2020-04-20T18:28:57.000Z',
                        'endDate' => '2020-04-20T18:28:57.000Z',
            )
        )
    );
    echo '<pre>' . print_r($array, true) . '</pre>';

    $csv_title = array();
    $csv_data = array();
    array2csv($array, $csv_title, $csv_data);
    $csv_title = implode(",", $csv_title);
    $csv_data = implode(",", $csv_data);

    echo $csv_title . "\n<br />" . $csv_data . "\n\n" . '
    <script>
    thevalue = \'' . $csv_title . "\\n\\\n" . $csv_data . '\'
    var blob = new Blob([thevalue], {type: \'attachment/csv\'});
    var blobUrl = URL.createObjectURL(blob);
    var a = document.createElement("a");
    with (a) {
        // href="data:attachment/" + extension + ";charset=utf-8;base64," + utf8_to_b64(thevalue);
        // href="data:text/" + save as + ";charset=\'utf-8\'," + thevalue;
        href=blobUrl
        download = "export.csv";
    }
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    </script>
    ';

function str_wrap($string = '', $char = '"') {
    return str_pad($string, strlen($string) + 2, $char, STR_PAD_BOTH);
}

function array2csv($array, &$csv_title, &$csv_data, $prefix = '') {
    foreach($array as $key => $value) {      
        if (!empty($prefix) || strlen($prefix) > 0)
            $key = $prefix . ' - ' . $key;
        if (is_array($value) && !empty($value))
            array2csv($value, $csv_title, $csv_data, $key);
        else {
            $csv_title[] = $key; //str_wrap($key);
            if (is_null($value))
                $csv_data[] = 'NULL';
            else if ($value === false)
                $csv_data[] = 0;
            else
                $csv_data[] = str_replace("\n", "\\n", $value);
                // $csv_data[] = str_wrap(addcslashes(str_replace("\n", "\\n", $value, ','));
        }
    }
}
?>
Disvalue answered 26/4, 2020 at 20:11 Comment(2)
Also added support for empty arrays like [something] => array().Disvalue
Also added support for Nulls, Boolean values and linebreaks.Disvalue

© 2022 - 2024 — McMap. All rights reserved.