tstenner
tstenner

Reputation: 10311

Converting keys of an array/object-tree to lowercase

I am currently optimizing a PHP application and found one function being called around 10-20k times, so I'd thought I'd start optimization there:

function keysToLower($obj)
{
    if (!is_object($obj) && !is_array($obj))
        return $obj;

    foreach ($obj as $key => $element) {
        $element = keysToLower($element);
        if (is_object($obj)) {
            $obj->{strtolower($key)} = $element;
            if (!ctype_lower($key))
                unset($obj->{$key});
        } elseif (is_array($obj) && ctype_upper($key)) {
           $obj[strtolower($key)] = $element;
           unset($obj[$key]);
        }
    }
    return $obj;
}

Most of the time is spent in recursive calls (which are quite slow in PHP), but I don't see any way to convert it to a loop. How can I do this?

Upvotes: 3

Views: 5389

Answers (7)

gaborous
gaborous

Reputation: 16620

You might also want to lookup array_change_key_case().

For objects, you can do:

($obj)array_change_key_case((arr)$o)

Upvotes: 3

mickmackusa
mickmackusa

Reputation: 48041

Here is a recursive function which modifies by reference to replace all keys/properties in an array or object structure that may contain any nested arrays or objects. It makes a copy of each level and overwrites the level after it is finished changing all keys.

Code: (Demo)

function allKeysToLower(array|object &$data): void
{
    $type = gettype($data);
    foreach ($data as $k => &$v) {
        if (is_array($v) || is_object($v)) {
            (__FUNCTION__)($v);  // go to deeper level
        }
        if (is_string($k)) {
            $k = strtolower($k);  // mutate the key
        }
        switch ($type) {
            case 'object':
                $new ??= (object) [];  // create object if not created, to allow population
                $new->{$k} = $v;  // add property to object
                break;
            case 'array':
                $new[$k] = $v;  // add element to array
        }
    }
    $data = $new ?? $data;  // fallback to empty $data if loop not entered
}

allKeysToLower($test);
var_export($test);

Upvotes: 0

Andrew Clark
Andrew Clark

Reputation: 1

A some what late response to a old thread but, there's a native function that does this, you could wrap it up something along these lines.

function setKeyCasing($thing, $case = CASE_LOWER) {
    return array_change_key_case((array) $thing, $case);
}

Upvotes: 0

medina
medina

Reputation: 8187

here a example using lambda:

$multiArrayChangeKeyCase = function (&$array) use (&$multiArrayChangeKeyCase) {
    $array = array_change_key_case($array);

    foreach ($array as $key => $row)
        if (is_array($row))
             $multiArrayChangeKeyCase($array[$key]);
};

Upvotes: 2

Oblio
Oblio

Reputation: 257

I assume you don't care about casting to array...

function keys_to_lower($o) {
    if (is_object($o)) {
        $o = (array)$o;
    }
    if (is_array($o)) {
        return array_map('keys_to_lower', array_change_key_case($o));
    }
    else {
        return $o;
    }
}

Upvotes: 2

Gumbo
Gumbo

Reputation: 655707

Foreach is using an internal copy that is then traversed. Try it without:

function keysToLower($obj)
{
    $type = (int) is_object($obj) - (int) is_array($obj);
    if ($type === 0) return $obj;
    reset($obj);
    while (($key = key($obj)) !== null)
    {
        $element = keysToLower(current($obj));
        switch ($type)
        {
        case 1:
            if (!is_int($key) && $key !== ($keyLowercase = strtolower($key)))
            {
                unset($obj->{$key});
                $key = $keyLowercase;
            }
            $obj->{$key} = $element;
            break;
        case -1:
            if (!is_int($key) && $key !== ($keyLowercase = strtolower($key)))
            {
                unset($obj[$key]);
                $key = $keyLowercase;
            }
            $obj[$key] = $element;
            break;
        }
        next($obj);
    }
    return $obj;
}

Or use references to avoid that a copy is used:

function &keysToLower(&$obj)
{
    $type = (int) is_object($obj) - (int) is_array($obj);
    if ($type === 0) return $obj;
    foreach ($obj as $key => &$val)
    {
        $element = keysToLower($val);
        switch ($type)
        {
        case 1:
            if (!is_int($key) && $key !== ($keyLowercase = strtolower($key)))
            {
                unset($obj->{$key});
                $key = $keyLowercase;
            }
            $obj->{$key} = $element;
            break;
        case -1:
            if (!is_int($key) && $key !== ($keyLowercase = strtolower($key)))
            {
                unset($obj[$key]);
                $key = $keyLowercase;
            }
            $obj[$key] = $element;
            break;
        }
    }
    return $obj;
}

Upvotes: 5

mario
mario

Reputation: 145512

array_combine(array_map("strtolower", array_keys($a)), array_values($a))

Upvotes: 1

Related Questions