代码之家  ›  专栏  ›  技术社区  ›  Jason

将嵌套数组中的“dot notation”键展开为子数组

  •  3
  • Jason  · 技术社区  · 6 年前

    我从一个任意深度的嵌套数组开始。在这个数组中,一些键是由点分隔的一系列标记。例如“billingaddress.street”或“foo.bar.baz”。我想将这些键控元素扩展到数组,所以结果是一个嵌套数组,其中所有键都被扩展。

    例如:

    [
        'billingAddress.street' => 'My Street',
        'foo.bar.baz' => 'biz',
    ]
    

    应扩展到:

    [
        'billingAddress' => [
            'street' => 'My Street',
        ],
        'foo' => [
            'bar' => [
                'baz' => 'biz',
            ]
        ]
    ]
    

    原始的“billingaddress.street”可以放在新的“billingaddress”数组旁边,但它不必是(因此解决方案可以在原始数组上操作或创建新数组)。“billingaddress.city”等其他元素可能需要添加到数组的同一扩展部分。

    一些键可能有两个以上的标记,由点分隔,因此需要进一步扩展。

    我看过 array_walk_recursive()

    array_map

    [
        'name' => 'Name',
        'address.city' => 'City',
        'address.street' => 'Street',
        'card' => [
            'type' => 'visa',
            'details.last4' => '1234',
        ],
    ]
    

    [
        'name' => 'Name',
        'address.city' => 'City', // Optional
        'address' => [
            'city' => 'City',
            'street' => 'Street',
        ],
        'address.street' => 'Street', // Optional
        'card' => [
            'type' => 'visa',
            'details.last4' => '1234', // Optional
            'details' => [
                'last4' => '1234',
            ],
        ],
    ]
    

    array

    3 回复  |  直到 6 年前
        1
  •  5
  •   trincot    6 年前

    您可能会发现,将组合(点)键分解后得到的键的顺序颠倒是很有用的。以这种相反的顺序,将以前的结果逐步包装到新数组中更容易,从而为一个点式键/值对创建嵌套的结果。

    最后,将部分结果与内置结果合并为累积的“大”结果 array_merge_recursive 功能:

    function expandKeys($arr) {
        $result = [];
        foreach($arr as $key => $value) {
            if (is_array($value)) $value = expandKeys($value);
            foreach(array_reverse(explode(".", $key)) as $key) $value = [$key => $value];
            $result = array_merge_recursive($result, $value);
        }
        return $result;
    }
    

    看到它运行 repl.it

        2
  •  4
  •   ggorlen Hoàng Huy Khánh    6 年前

    这是一个递归的尝试。请注意,这不会删除旧的键,不会维护任何键顺序,并且忽略该类型的键 foo.bar.baz .

    function expand(&$data) {
      if (is_array($data)) {
        foreach ($data as $k => $v) {
          $e = explode(".", $k);
    
          if (count($e) == 2) {
            [$a, $b] = $e;
            $data[$a][$b]= $v;
          }
    
          expand($data[$k]);
        }
      }
    }
    

    结果:

    Array
    (
        [name] => Name
        [address.city] => City
        [address.street] => Street
        [card] => Array
            (
                [type] => visa
                [details.last4] => 1234
                [details] => Array
                    (
                        [last4] => 1234
                    )
    
            )
    
        [address] => Array
            (
                [city] => City
                [street] => Street
            )
    
    )
    

    说明:

    在函数的任何调用中,如果参数是数组,则迭代键和值以查找具有 . 在他们里面。对于任何这样的键,展开它们。对数组中的所有键递归调用此函数。

    完整版:

    这里有一个完整的版本支持多个 . 然后清理钥匙:

    function expand(&$data) {
      if (is_array($data)) {
        foreach ($data as $k => $v) {
          $e = explode(".", $k);
          $a = array_shift($e);
    
          if (count($e) == 1) {
            $data[$a][$e[0]] = $v;
          }
          else if (count($e) > 1) {
            $data[$a][implode(".", $e)] = $v;
          }
        }
    
        foreach ($data as $k => $v) {
          expand($data[$k]);
    
          if (preg_match('/\./', $k)) {
            unset($data[$k]);
          }
        }
      }
    }
    

    这里有一个 repl .

        3
  •  2
  •   Jason    6 年前

    @trincot的另一个解决方案被认为更优雅,我现在使用的是这个解决方案。

    这是我的解决方案,它扩展了@ggorlen给出的解决方案和提示

    我采取的方法是:

    • 创建一个新数组,而不是对初始数组进行操作。
    • 不需要保留旧的预扩展元素。如果需要,可以很容易地添加它们。
    • 扩展键从根数组一次完成一个级别,剩余的扩展以递归方式返回。

    类方法:

    protected function expandKeys($arr)
    {
        $result = [];
    
        while (count($arr)) {
            // Shift the first element off the array - both key and value.
            // We are treating this like a stack of elements to work through,
            // and some new elements may be added to the stack as we go.
    
            $value = reset($arr);
            $key = key($arr);
            unset($arr[$key]);
    
            if (strpos($key, '.') !== false) {
                list($base, $ext) = explode('.', $key, 2);
    
                if (! array_key_exists($base, $arr)) {
                    // This will be another array element on the end of the
                    // arr stack, to recurse into.
    
                    $arr[$base] = [];
                }
    
                // Add the value nested one level in.
                // Value at $arr['bar.baz.biz'] is now at $arr['bar']['baz.biz']
                // We may also add to this element before we get to processing it,
                // for example $arr['bar.baz.bam']
    
    
                $arr[$base][$ext] = $value;
            } elseif (is_array($value)) {
                // We already have an array value, so give the value
                // the same treatment in case any keys need expanding further.
    
                $result[$key] = $this->expandKeys($value);
            } else {
                // A scalar value with no expandable key.
    
                $result[$key] = $value;
            }
        }
    
        return $result;
    }
    
    $result = $this->expandKeys($sourceArray)