Are there functions similar to Ruby's `dig` in other languages?

Omar Bahareth on August 22, 2019

I really like using Ruby's Array#dig and Hash#dig operator (introduced in Ruby 2.3) to quickly and safely access deeply nested structures I would ... [Read Full]
markdown guide
 

It's known as optional chaining / null propagation.
In Javascript it's described as ECMAScript proposal

instead of writing:

const phone = orders && orders[0] && orders[0].customer && orders[0].customer.phone

it could be like:

const phone = orders?[0]?.customer?.phone

For now, there are libraries like lodash/get, where we can use function:

import { get } from 'lodash'
const phone = get(orders[0], 'customer.phone', 'optional default value')
 

I'm currently doing Rust, and I have a little trouble answering this question.

A couple points:

  • Rust does not have anything like dig built-in.
  • It wouldn't make sense, as there is no nil/null/whatever in Rust - if it's a String, it's there, no strings attached (pun intended).
  • To represent a value that might not be there, there is a concept of an Option enum, which can be either Some(value) or None (the concept is not new - Haskell has Maybe, and languages like Swift or Kotlin have nullable types)
  • A macro can probably do something like that for nested Options.

I came up with this:

macro_rules! dig {
   ( $root:ident, $($step:ident),+ $(,)? ) => {
      {
         let leaf = $root;
         $(
            let leaf = leaf.and_then(|inner| inner.$step);
         )+
         leaf
      }
   }
}

Looks a bit funky, but does the job:

let c = Some(C {                                   
    b: Some(B {                                    
        a: Some(A { data: 5 }),                    
    }),                                            
});                                                
assert_eq!(Some(5), dig!(c, b, a).map(|a| a.data));

let c = Some(C { b: None });                    
assert_eq!(None, dig!(c, b, a).map(|a| a.data));

EDIT: I completely forgot about a cool language feature in Rust - ? operator.

Synopsis:

Result<T, E> is very similar to Option<T>, but it instead can be either Ok(T) (all good, T is the value) or Err(E) (something went wrong, E is the error type - String for a message, etc.)

result? basically means .if it's Ok, get the inner value and continue execution. If it's an Err, return it".

It allows for code like this:

fn etl(data: ...) -> Result<..., Error> {
    data.extract()?.transform()?.load()
}
 

C# 6.0 and onwards gives you the Null Propagating Operator MSDN Guide — like many of the other languages here, you would use it something like:

var customerName = orders[0]?.customer?.name;

This can be combined with the null coalescing operator, to give a default value:

var customerName = orders[0]?.customer?.name ?? "No value found.";
 

For Python, glom is a great package for this sort of nested data access. The happy path from the intro docs looks like this:

from glom import glom

target = {'a': {'b': {'c': 'd'}}}
glom(target, 'a.b.c')  # returns 'd'

That 'a.b.c' bit is a simple glom specifier, but it can get much wilder! Glom provides more advanced specifiers for features like:

  • Null coalescing behavior
  • Validation
  • Debugging/inspection

And you can always plug in your own code or extend glom even further. Simple to get started, but a long fun road to travel from there!

 

I'm not sure that ECMAScript 10/ES10/Javascript 2019 has a function like that, per se, but one new feature that will likely be in ES11 that would help you get the safe-access bit of these dig functions in JS would be using the new proposed nullish-coalescing syntax.

The current version of Swift does already have null-coalescing, though.

 

PHP7 introduced the null coalesce operator (??) which will suppress errors for the missing intermediate keys. So, this will work without errors/warnings:

$phone = $orders[0]['customer']['phone'] ?? null;

The Laravel framework in PHP also has array helpers which allow you to access values using "dot" notation:

$phone = array_get($orders, '0.customer.phone');

You can pass a third value to use as the default if it doesn't exist, but the default is null without it.

This helper function is a shortcut to access the Arr::get() method mentioned by Suhayb Alghutaymil.

 

In Java (version 8 and onward) this can be done using Optional

String phoneNumber = Optional.ofNullable(list.get(0))
                             .map(Order::getCustomer)
                             .map(Customer::getPhone)
                             .orElse(null);
 

In Laravel you could use this with Arr::get helper method.

use Illuminate\Support\Arr;

$array = ['products' => ['desk' => ['price' => 100]]];

$price = Arr::get($array, 'products.desk.price');

// 100
 

Haskell and other functional languages have Lenses, which are roughly equivalent to focusing on a value or path and either getting or setting it. Imagine dig with a related bury function and you get close.

I'd mentioned it in this particular Storybook post in Part Four, though it may make more sense to start at Part One for continuity

 
 

In Elixir use Kernel.get_in/2 for maps

iex(1)> m = %{foo: %{bar: %{baz: 1}}}
%{foo: %{bar: %{baz: 1}}}
iex(2)> get_in m, [:foo, :bar, :baz]
1
iex(3)> get_in m, [:foo, :zot]
nil
 

Thank you for sharing everyone! I learned a lot!

 

I'm a heavy user of Ramda's #path method in JavaScript / Node

code of conduct - report abuse