# Replicating Ruby's dig() in Julia

**URL:** https://discourse.julialang.org/t/replicating-rubys-dig-in-julia/20551
**Category:** General Usage
**Tags:** question
**Created:** [February 7, 2019, 3:10pm UTC](https://discourse.julialang.org/t/replicating-rubys-dig-in-julia/20551 "2019-02-07T15:10:49Z")
**Posts on this page:** 7
**Page:** 1

<div class="post-metadata">

### Author: ![Amval](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/amval/32/6217_2.png) [@Amval](https://discourse.julialang.org/u/Amval)
#### Post date: [February 7, 2019, 3:10pm UTC](https://discourse.julialang.org/t/replicating-rubys-dig-in-julia/20551/1 "2019-02-07T15:10:49Z")

</div>

Hello,

in Ruby there’s a method useful for navigating nested dictionaries called [dig()](https://ruby-doc.org/core-2.3.0_preview1/Hash.html#method-i-dig)

```julia
nested = Dict(
    :a => Dict(
        :b => "b",
        :c => Dict(
            :d => "d"
        )
    )
)

dig(nested, :a, :b) #=> "b"
dig(nested, :a, :c, :d) #=> "d"
dig(nested, :a, :e) #=> nothing

```

I have written this function. The problem is that `args...` will produce a Tuple. When called recursively, i will have a Tuple of Tuples and I don’t know how to flatten it or to avoid this. I can fix it by making `args::Tuple` and calling like this: `dig(nested, (:a, :b))`, but I find this somewhat uglier.

```julia
function dig(dict::Dict, args...)
    if haskey(dict, args[1])
        aux = dict[args[1]]
        if length(args[2:end]) > 0
            dig(access, args[2:end])
        else
            aux
        end
    end
 end

```

How can I “splat” args when maing the recursive call or flatten the Tuple?

---

<div class="post-metadata">

### Author: ![tanhevg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tanhevg/32/12025_2.png) [@tanhevg](https://discourse.julialang.org/u/tanhevg)
#### Post date: [February 7, 2019, 3:17pm UTC](https://discourse.julialang.org/t/replicating-rubys-dig-in-julia/20551/2 "2019-02-07T15:17:07Z")

</div>

```julia
function dig(dict::Dict, args...)
    d = dict
    for key in args
        if haskey(d, key)
            d = d[key]
        else
            return nothing
        end
    end
    d
end

```

---

<div class="post-metadata">

### Author: ![yha](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yha/32/3502_2.png) [@yha](https://discourse.julialang.org/u/yha)
#### Post date: [February 7, 2019, 3:18pm UTC](https://discourse.julialang.org/t/replicating-rubys-dig-in-julia/20551/3 "2019-02-07T15:18:03Z")

</div>

```julia
dig(dict::AbstractDict, key, keys...) = dig(dict[key], keys...)
dig(x) = x

```

This throws KeyError rather than returning `nothing` on missing key, but should be easy to modify if you really want to return `nothing` as in your example.  
EDIT: alternately

```julia
dig2(dict::AbstractDict, keys...) = foldl(getindex, keys; init=dict)

```

---

<div class="post-metadata">

### Author: ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)
#### Post date: [February 7, 2019, 7:27pm UTC](https://discourse.julialang.org/t/replicating-rubys-dig-in-julia/20551/4 "2019-02-07T19:27:03Z")

</div>

> [@Amval](#):
>
> dig(nested, :a, :b)

```julia
dig(x) = x
dig(d::AbstractDict, key, keys...) = dig(get(d, key, nothing), keys...)

```

`get(dict, key, default)` is really nice for dictionaries, and also faster than first checking (`haskey`) and then retrieving.

**Edit:** The `foldl` solution is _much_ faster, but fails when the key isn’t found:

```julia
julia> dig2(d, :a, :e)

ERROR: KeyError: key :e not found

```

---

<div class="post-metadata">

### Author: ![Amval](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/amval/32/6217_2.png) [@Amval](https://discourse.julialang.org/u/Amval)
#### Post date: [February 8, 2019, 10:52am UTC](https://discourse.julialang.org/t/replicating-rubys-dig-in-julia/20551/5 "2019-02-08T10:52:25Z")

</div>

Thank you all four your answers.

@DNF I didn’t find `foldl` to be much faster, at least after doing the necessary modifications so it doesn’t fail when the key isn’t found. And @yha first approach I’d say is conceptually simpler. Your solution is closer, although it would fail when passed `:a, :b, :c` if `:b`doesn’t exist already.

Can be fixed like this:

```julia
dig(x) = x
dig(x::Nothing, keys...) = nothing
dig(d::AbstractDict, key, keys...) = dig(get(d, key, nothing), keys...)

```

But maybe I shouldn’t be trying to navigate through empty keys on the first place…

---

<div class="post-metadata">

### Author: ![JoshuaWhittemore](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/joshuawhittemore/32/12189_2.png) [@JoshuaWhittemore](https://discourse.julialang.org/u/JoshuaWhittemore)
#### Post date: [September 22, 2020, 11:35pm UTC](https://discourse.julialang.org/t/replicating-rubys-dig-in-julia/20551/6 "2020-09-22T23:35:29Z")

</div>

This is just what I was looking for. One nice thing about Ruby’s `dig` is it can be used to access nested arrays as well as dicts. This is handy for working with responses from Elasticsearch. So I would expand @Amval’s answer to:

```julia
dig(x) = x
dig(x::Nothing, keys...) = nothing
dig(d::AbstractDict, key, keys...) = dig(get(d, key, nothing), keys...)
dig(a::AbstractArray, i::Integer, keys...) = checkbounds(Bool, a, i) ? dig(a[i], keys...) : nothing

```

Then it can do:

```julia
julia> nested = Dict(
           :a => [
               Dict(:b => "b"),
               Dict(:c => "c"),
               Dict(:d => "d")
           ]
       )

julia> dig(nested, :a, 2, :c)
"c"

```

---

<div class="post-metadata">

### Author: ![benninkrs](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/benninkrs/32/3191_2.png) [@benninkrs](https://discourse.julialang.org/u/benninkrs)
#### Post date: [September 23, 2020, 12:33am UTC](https://discourse.julialang.org/t/replicating-rubys-dig-in-julia/20551/7 "2020-09-23T00:33:44Z")

</div>

You may be interested in [Setfield.jl](https://github.com/jw3126/Setfield.jl) which implements a “lens” API that looks similar to what you’re doing.
