remaining path(s). foreach ( $key_paths as $path ) { $path = (string) $path; $dot = strpos( $path, '.' ); if ( false === $dot ) { $flat_lower[] = strtolower( $path ); } else { $head = strtolower( substr( $path, 0, $dot ) ); $tail = substr( $path, $dot + 1 ); $nested[ $head ][] = $tail; } } $is_object = is_object( $value ); $arr = $is_object ? (array) $value : $value; $result = array(); foreach ( $arr as $key => $item ) { $key_lower = strtolower( (string) $key ); if ( in_array( $key_lower, $flat_lower, true ) ) { // Flat match: transform or replace with placeholder. $result[ $key ] = null !== $transform ? $transform( $item ) : $placeholder; } elseif ( isset( $nested[ $key_lower ] ) ) { // Dot-path match: recurse on the nested tails. $result[ $key ] = self::redact( $item, $nested[ $key_lower ], $placeholder, $transform ); } else { // No match: still recurse to handle arrays nested inside. $result[ $key ] = ( is_array( $item ) || is_object( $item ) ) ? self::redact( $item, $key_paths, $placeholder, $transform ) : $item; } } return $is_object ? (object) $result : $result; } }