Lukas_Skywalker
Lukas_Skywalker

Reputation: 2070

Sorbet signature for nested hash

I have a method that parses YAML files. The returned object is a nested Hash, where the keys are always Strings and the leaf-values are always strings, e.g.

{
    "a" => "foo",
    "b" => {
        "c" => "bar",
        "d" => "baz"
    }
}

I don't know in advance how deep the hash is.

The closest I got to typing the return value was the following signature:

T.any(T::Hash[String,String], T::Hash[String,T::Hash[String, T.untyped]])

This is obviously a bad solution, since it doesn't check anything beneath the second nesting, but the documentation about custom types seems a bit sparse.

Is there any way to type nested hashes, using a custom type, nested types or something similar?

Upvotes: 2

Views: 1402

Answers (1)

marianosimone
marianosimone

Reputation: 3606

Unfortunately, you won't be able to do much more than what you got to at this point. Even though Shapes are supported, they are an experimental feature.

If you really want to go with hashes, you could express it as:

MyType = T.type_alias {T::Hash[String, T.any(String, T::Hash[String, T.untyped])]}

Alternatively, you could use a T::Struct:

class MyType < T::Struct
  const :key, String
  const :value, T.any(String, MyType)
end

You'd still have the uncertainty of what the type of the value is, but with flow sensitivity, it should be easy to process the structure:

class Processor
  extend T::Sig
  
  sig {params(my_object: MyType).returns(String)}
  def process(my_object)
    key = my_object.key
    obj_value = my_object.value # this is needed for flow sensitivity below
    value = case obj_value
    when String
      value
    when MyType
      process(obj_value)
    else
      T.absurd(obj_value) # this makes sure that if you add a new type to `value`, Sorbet will make you handle it
    end
    return "key: #{key}, value: #{value}"
  end
end

Upvotes: 2

Related Questions