Преобразование ключей массива из underscore_case в camelCase рекурсивно

Мне пришлось придумать способ преобразования ключей массива с помощью undescores (underscore_case) в camelCase. Это нужно было сделать рекурсивно, так как я не знал, какие массивы будут переданы методу.

Я придумал это:

private function convertKeysToCamelCase($apiResponseArray) { $arr = []; foreach ($apiResponseArray as $key => $value) { if (preg_match('/_/', $key)) { preg_match('/[^_]*/', $key, $m); preg_match('/(_)([a-zA-Z]*)/', $key, $v); $key = $m[0] . ucfirst($v[2]); } if (is_array($value)) $value = $this->convertKeysToCamelCase($value); $arr[$key] = $value; } return $arr; } 

Он выполняет эту работу, но я думаю, что это можно сделать гораздо лучше и сжато. Несколько вызовов preg_match а затем конкатенация просто выглядят странно.

Вы видите способ убрать этот метод? И что еще более важно, возможно ли вообще выполнить одну операцию с одним вызовом preg_match ? Как это будет выглядеть?

Рекурсивная часть не может быть упрощена или преувеличена.

Но преобразование из underscore_case (также известного как snake_case ) и camelCase может быть выполнено несколькими различными способами:

 $key = 'snake_case_key'; // split into words, uppercase their first letter, join them, // lowercase the very first letter of the name $key = lcfirst(implode('', array_map('ucfirst', explode('_', $key)))); 

или

 $key = 'snake_case_key'; // replace underscores with spaces, uppercase first letter of all words, // join them, lowercase the very first letter of the name $key = lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $key)))); 

или

 $key = 'snake_case_key': // match underscores and the first letter after each of them, // replace the matched string with the uppercase version of the letter $key = preg_replace_callback( '/_([^_])/', function (array $m) { return ucfirst($m[1]); }, $key ); 

Выберите свой любимый!

Я могу быстро определить две отдельные задачи. Один из них – преобразовать строки в формат верблюда, а другой – сопоставить ключи многомерного массива. Эти задачи не имеют никакого отношения к другому, и поэтому лучше всего реализовать их как отдельные функции.

Давайте начнем с функции более высокого порядка mapArrayKeys . Он примет функцию сопоставления и применит эту функцию к каждому ключу массива, создав новый массив. Мы должны ожидать, что функция отображения будет инъективной (взаимно однозначной).

 function mapArrayKeys(callable $f, array $xs) { $out = array(); foreach ($xs as $key => $value) { $out[$f($key)] = is_array($value) ? mapArrayKeys($f, $value) : $value; } return $out; } 

Есть несколько нерешительных бит, которые я не считаю столь важными. Возможно, вам не захочется вводить намеки на параметры, хорошо. Может быть, вы предпочтете, если / then / else вместо тернарного оператора, хорошо. Важно то, что с помощью mapArrayKeys вы можете применить любую (инъективную) функцию сопоставления к клавишам массива.

Вторая задача – преобразовать строки в верблюд-футляр. Для этого вы можете использовать функции PCRE, это нормально. Я собираюсь использовать explode чтобы сделать расщепление.

 function underToCamel($str) { return lcfirst(implode('', array_map('ucfirst', explode('_', $str)))); } 

Теперь эти две функции могут использоваться в тандеме для достижения общей цели преобразования ключей массива из подчёркивания в формат верблюда.

 mapArrayKeys('underToCamel', array('foo_bar' => array ('baz_qux' => 0))); 

Заметка об инъективности. Функция underToCamel не обязательно инъективна, поэтому вам нужно проявлять особую осторожность. Вы должны предположить, что для всех x_y и всех xY (где Y – это xY версия y), что ровно один из x_y , xY , x_Y является x_Y подчеркивания (то же самое следует за более подчеркиванием).

Например, underToCamel("foo_bar") == "fooBar" и underToCamel("fooBar") == "fooBar" и underToCamel("foo_Bar") == "fooBar" и поэтому только один может быть допустимым underToCamel("foo_Bar") == "fooBar" подчеркивания.

Считываемость вложенных функций

Это ответ на комментарий luqo33 .

То, что я подразумевал под «слишком сложным» (по крайней мере, на мой взгляд), заключается в том, что в этом решении используется множество вложенных функций (например, четыре функции, называемые в underToCamel, все вложенные – затрудняет чтение).

Строка кода, о которой идет речь, такова.

 lcfirst(implode('', array_map('ucfirst', explode('_', $str)))); 

Я оспариваю, что это читаемо. Я действительно признаю, что этот стиль не типичен для PHP, и я думаю, поэтому читатель PHP может быть отложен.

Сначала следует отметить, что вложенные функции на самом деле не такие ненормальные, как вы думаете. Рассмотрим математическое выражение.

 (-$b + sqrt($b*$b - 4*$a*$c)) / (2*$a) 

Это выражение, которое использует множество вложенных функций: +, -, *, / . Если вы притворяетесь, что у вас не было BEDMAS (или эквивалента), встроенного в ваше подсознание, это на самом деле сложное выражение для понимания – есть неявные правила, которые вы подсознательно применяете, чтобы знать, что сначала вы делаете материал в круглых скобках, затем умножения , и так далее. Ничто из этого не кажется сложным, потому что вы научились читать такие выражения, и теперь это часть вашего репертуара. То же самое касается чтения выражений, подобных тому, которое я использовал.

Я могу переписать выражение так, чтобы в каждой строке использовалась одна функция.

 $a = explode('_', $str); $b = array_map('ucfirst', $a); $c = implode('', $b); $d = lcfirst($c); 

Теперь порядок выполнения читается сверху вниз. Я также могу написать его, чтобы читать снизу вверх.

 lcfirst( implode('', array_map('ucfirst', explode('_', $str )))); 

Наконец, я могу написать его для чтения справа налево или изнутри (если вы считаете круглые скобки), как это было изначально написано.

 lcfirst(implode('', array_map('ucfirst', explode('_', $str)))); 

Все эти версии используют простой шаблон, называемый функцией композиции , что является еще одной причиной, которую легко читать и понимать. С составом функций вы можете создать последовательность функций, в которых каждая функция будет подаваться с выхода предыдущей функции.

Чтобы объяснить этот сценарий, моя последовательность функций в порядке слева направо – explode '_', array_map 'ucfirst', implode '', lcfirst . То, как это работает, может быть ясно видно из версии, которая использует переменные $a через $d . Вы бросаете что-то в explode '_' , и результат от этого передается в array_map 'ucfirst' , а затем в implode '' и, наконец, в lcfirst . Вы могли бы думать об этом как о трубопроводе, о сборочной линии или о чем-то подобном.

Вы можете использовать preg_replace_callback и изменять все ключи, не array_keys каждый из них с помощью array_keys и array_combine :

 private function convertKeysToCamelCase($apiResponseArray) { $keys = preg_replace_callback('/_(.)/', function($m) { return strtoupper($m[1]); }), array_keys($apiResponseArray)); return array_combine($keys, $apiResponseArray); } 

или без регулярного выражения:

 private function convertKeysToCamelCase($apiResponseArray) { $keys = array_map(function ($i) { $parts = explode('_', $i); return array_shift($parts). implode('', array_map('ucfirst', $parts)); }, array_keys($apiResponseArray)); return array_combine($keys, $apiResponseArray); } 

Вы можете изменить вторую функцию для работы с многомерными массивами:

 private function convertKeysToCamelCase($apiResponseArray) { $keys = array_map(function ($i) use (&$apiResponseArray) { if (is_array($apiResponseArray[$i])) $apiResponseArray[$i] = $this->convertKeysToCamelCase($apiResponseArray[$i]); $parts = explode('_', $i); return array_shift($parts) . implode('', array_map('ucfirst', $parts)); }, array_keys($apiResponseArray)); return array_combine($keys, $apiResponseArray); } 

Попробуйте preg_replace_callback() :

 $key = preg_replace_callback('/_([az]*)/', function($matches) { return ucfirst($matches[1]); }), $key); 

Пара отмечает:

  • вам нужно искать только [az] потому что [AZ] уже будет заглавными
  • * Повторение 0+ использовалось, если мы имеем ситуацию типа camel_$case , я предположил, что _ все еще нужно заменить
  • вот демонстрация выражения, которое не запускает ucfirst() в первой группе совпадений

Вот еще один подход, array_walk_recursive() preg_replace_callback методов array_walk_recursive() и preg_replace_callback 🙂

 function convertKeysToCamelCase($array) { $result = []; array_walk_recursive($array, function ($value, &$key) use (&$result) { $newKey = preg_replace_callback('/_([az])/', function ($matches) { return strtoupper($matches[1]); }, $key); $result[$newKey] = $value; }); return $result; } 

Если вы не хотите делать это с помощью регулярного выражения:

 + (NSString*)camelFromUnderscore:(NSString*)underscore { NSMutableArray* components = [NSMutableArray arrayWithArray:[underscore componentsSeparatedByString:@"_"]]; NSString* firstComponent = [components objectAtIndex:0]; [components removeObjectAtIndex:0]; NSString* remainingComponents = [[[components componentsJoinedByString:@" "] capitalizedString] stringByReplacingOccurrencesOfString:@" " withString:@""]; NSString* name = [NSString stringWithFormat:@"%@%@", firstComponent, remainingComponents]; return name; } 
 function convertToCamelCase($array){ $finalArray = array(); foreach ($array as $key=>$value): if(strpos($key, "_")) $key = lcfirst(str_replace("_", "", ucwords($key, "_"))); //let's convert key into camelCase if(!is_array($value)) $finalArray[$key] = $value; else $finalArray[$key] = $this->_convertToCamelCase($value ); endforeach; return $finalArray; } 

Конвертируемая клавиша convertToCamelCase преобразует массив ключей в корпус верблюда

Например, вы можете использовать это как:

  $finalArray = convertToCamelCase($array); print_r(finalArray);