phparraysjsoncsvcurl

Flatten JSON array with multiple items - how to fit to a single csv cell?


I must get some data via API using curl and save it to a CSV file. Example JSON data set I'm getting when connected via API:

{
  "id": 24598942,
  "created_at": "2021-08-16T15:59:25.345+01:00",
  "success": true,
  "payment_amount": 90,
  "invoice_ids": [
    1646569091
  ],
  "ref_num": "2858",
  "payment_method": "BACS"
}

My PHP code:

$url = 'https://example.com/?page='.$x;

$curl = curl_init($url);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/json', $auth_key_here));
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "GET");
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

$query = curl_exec($curl);
curl_close($curl);

$decoded = json_decode($data, true);
    
if ($x == 1) {
    $fp = fopen($csv, 'w');
    fputcsv($fp, array_keys($decoded[0]));
    fclose($fp);
}
    
$fp2 = fopen($csv, 'a');
    
foreach ($decoded as $fields) {
    fputcsv($fp2, array_flatten($fields));
    $row_count++;
}
fclose($fp2);

function array_flatten($nonFlat) {
    $flat = array();
    foreach (new RecursiveIteratorIterator(new RecursiveArrayIterator($nonFlat)) as $k=>$v) {
        $flat[$k] = $v;
    }
    return $flat;
}

This gives me "invoice_ids" array flatten so I can see actual data in CSV file rather than "Array" word. But some records contain multiple invoice ids in array:

{
  "id": 21016112,
  "created_at": "2020-08-17T16:00:55.552+01:00",
  "success": true,
  "payment_amount": 90,
  "invoice_ids": [
    1646540900,
    1646277088
  ],
  "ref_num": "ZN905TNVCNGQY",
  "payment_method": "Credit Card"
}

This messes up CSV file by moving some data into the wrong columns, as depicted here: csv file

How to alter the PHP code so the flat array with multiple invoice ids fit to a single column in CSV file?


Solution

  • In this case you don't want to flatten each row because it has a fixed number of keys that match the number of columns in the CSV. Instead, if any of the CSV field values are an array, you want to flatten those into a string, otherwise, preserve it as is.

    Here's code to do that using array_walk() that will change any column values that are arrays to a comma separated string:

    foreach ($decoded as $fields) {
        array_walk($fields, function(&$value, $key) {
            if (is_array($value)) {
                $value = join(',', $value);
            }
        });
        fputcsv($fp2, $fields);
        $row_count++;
    }
    fclose($fp2);
    

    In the case of your example, after running it through array_walk, it will become:

    array(7) {
      ["id"]=>
      int(21016112)
      ["created_at"]=>
      string(29) "2020-08-17T16:00:55.552+01:00"
      ["success"]=>
      bool(true)
      ["payment_amount"]=>
      int(90)
      ["invoice_ids"]=>
      string(21) "1646540900,1646277088"
      ["ref_num"]=>
      string(13) "ZN905TNVCNGQY"
      ["payment_method"]=>
      string(11) "Credit Card"
    }