Convert array keys from underscore_case to camelCase recursively

前端 未结 6 2090
一向
一向 2020-12-31 17:59

I had to come up with a way to convert array keys using undescores (underscore_case) into camelCase. This had to be done recursively since I did not know what arrays will be

6条回答
  •  野趣味
    野趣味 (楼主)
    2020-12-31 18:24

    I can quickly spot two separate tasks. One is to convert strings to camel-case format, and the other is to map the keys of a multi-dimensional array. These tasks have nothing to do with the other, and so it is best to implement them as separate functions.

    Lets start with a higher-order function mapArrayKeys. It will accept a mapping function and apply this function to each key of the array, producing a new array. We must expect the mapping function to be injective (one-to-one).

    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;
    }
    

    There are a few fiddly bits that I do not consider so important. You may not want to do type hinting on the parameters, okay fine. Maybe you'd rather if/then/else instead of a ternary operator, okay fine. What is important is that with mapArrayKeys you can apply any (injective) mapping function to array keys.

    The second task is to convert strings to camel-case. You might use PCRE functions for this, that's fine. I am going to use explode to do the splitting.

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

    Now these two functions can be used in tandem to achieve the overall goal of converting array keys from underscore to camel-case format.

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

    A note on injectivity. The function underToCamel is not necessarily injective, so you have to take special care. You have to assume that for all x_y and all xY (where Y is the capitalized version of y) that exactly one of x_y, xY, x_Y is an underscore format (same follows more more underscores).

    So for example, underToCamel("foo_bar") == "fooBar" and underToCamel("fooBar") == "fooBar" and underToCamel("foo_Bar") == "fooBar" and therefore only one can be a valid underscore format.

    Readability of nested functions

    This is in response to a comment by luqo33.

    What I meant by 'too complex' (at least in my view) is that this solution uses a lot of nested functions (e.g. four functions called in underToCamel, all nested - hinders readibility).

    The line of code in question is this.

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

    I contest that this is readable. I do acknowledge that this style is not typical for PHP, and I think that is why the PHP reader may be put off.

    First it should be noted that nested functions are not actually as abnormal as you'd think. Consider a math expression.

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

    This is an expression that uses a lot of nested functions: +, -, *, /. If you pretend you haven't had BEDMAS (or equivalent) embedded into your subconscious, this is actually a complicated expression to understand -- there are implicit rules you are subconsciously applying to know that first you do the stuff in parentheses, then the multiplications, and so on. None of this seems complicated because you have learned how to read such expressions and it is now part of your repertoire. The same goes for reading expressions like the one I used.

    I can rewrite the expression so that there is one function used per line.

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

    Now the execution order is read top-to-bottom. I can also write it to read bottom-to-top.

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

    Lastly I can write it to read right-to-left or inside-to-out (if you consider the parentheses), which is how it was originally written.

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

    All of these versions use a simple pattern called function composition, which is another reason it is easy to read and understand. With function composition you can build up a sequence of functions where each function feeds from the output of the previous function.

    To explain this scenario, my sequence of functions in left-to-right order is explode '_', array_map 'ucfirst', implode '', lcfirst. The way it works can be clearly seem from the version which uses the variables $a through $d. You throw something into explode '_', and the result from that is passed into array_map 'ucfirst', and then into implode '', and finally into lcfirst. You could think of this as a pipeline, or an assembly line, or something like that.

提交回复
热议问题