递归将数组键从下划线命名法转换为驼峰命名法

10

我不得不想出一种方法,将数组键使用下划线(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来执行相同的操作?那会是什么样子?

6个回答

16

递归部分无法进一步简化或美化。

但是从下划线命名法(也称为蛇形命名法)和驼峰命名法之间的转换可以用几种不同的方式进行:

$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
);

选择你喜欢的!


的确是个难题。然而,我决定接受这个答案,因为 '/_([^_])/', 考虑到了数组键包含字母和下划线以外的字符的情况 - 这当然涉及到第三个选项 :)。 - luqo33

6

您可以使用preg_replace_callback,而无需在每个键上循环使用array_keysarray_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);
}

我可能错了,但我认为array_keys不会返回嵌套数组键,因此它在这种形式下无法用于递归操作。至少我无法在嵌套数组上使其工作。 - luqo33
@luqo33:确实,这些函数只适用于数组的基本层级。 - Casimir et Hippolyte
@luqo33:我添加了第二个函数的递归版本。 - Casimir et Hippolyte

6
我可以快速发现两个不同的任务。一个是将字符串转换为驼峰格式,另一个是映射多维数组的键。这些任务互不相关,因此最好将它们实现为单独的函数。
让我们从一个高阶函数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;
}

有一些琐碎的细节我认为并不重要。如果您不想对参数进行类型提示,那没问题。也许您更喜欢用if/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是y的大写版本),恰好有一个x_yxYx_Y是下划线格式(同样适用于更多下划线)。
例如,underToCamel("foo_bar") == "fooBar"underToCamel("fooBar") == "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。您可以将其视为管道、装配线或类似的东西。

1
我有印象,这使得它比实际需要的更加复杂。 - luqo33
你能解释一下为什么你有那种印象吗?我特意让这个解决方案变得实用和简单,所以我想有机会解释一下它的原因。 - erisco
我没有完整地阅读答案,但我给你一个+1是因为你意识到underToCamel()与映射数组键是不同的。 - Sam
@erisco 当然,这些可以分成单独的任务 - 对此给予认可。我所说的“过于复杂”(至少在我的看法中)是指该解决方案使用了许多嵌套函数(例如,在underToCamel中调用了四个函数,全部嵌套 - 阻碍了可读性)。此外,我认为当将其馈送到非常大的数组时(主要由于对每个元素调用了众多函数),这种解决方案在内存方面会非常昂贵。此外,我从未来维护此代码的人的角度来看待它。 - luqo33
感谢您的详细解释,luqo33。嵌套函数:从风格上讲,这在PHP中是不正常的,但您可以轻松学习如何阅读它。内存成本:在函数返回后,堆栈内存被释放,因此随着添加更多键,内存不会累积。可维护性:我认为这里的问题是可理解性;我将在我的答案中添加更多内容,以澄清如何阅读嵌套函数。 - erisco

2
这里有另一种方法,利用array_walk_recursive()preg_replace_callback方法,以最简单的方式进行操作 :)
function convertKeysToCamelCase($array)
{
    $result = [];

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

        $result[$newKey] = $value;
    });

    return $result;
}

1
这看起来也是一个不错的解决方案。array_walk_recursive使得无需递归调用convertKeysToCamelCase方法。 - luqo33
依赖于你的代码@luqo33 :) 我的意思是,如果你需要多次执行相同的操作,则最好将其封装在一个通用的静态方法中。 - Ali
1
这将返回一个扁平的数组而不是多维数组,我没有深入探究,但肯定出了问题。 - Serhii Popov

1
尝试使用 preg_replace_callback() 函数:
$key = preg_replace_callback('/_([a-z]*)/', function($matches) {
    return ucfirst($matches[1]);
}), $key);

一些注意事项:

  • 只需要查找[a-z],因为[A-Z]已经是大写的了
  • *表示0+重复,这是为了处理像camel_$case这样的情况,我假设_仍然需要替换
  • 这里有一个表达式演示, 它不在第一个匹配组上运行ucfirst()

0
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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接