Search code examples
crystal-langtype-alias

Type alias and Hash as method parameter


I'm trying to create an initializer for a Class that receives a Hash as parameter. The Hash is a {String => Type} hash, and can be nested. I'm getting an error when running this code:

#file: types.cr
class Types
  alias Type = Nil |
               Bool |
               Int32 |
               Int64 |
               Float64 |
               String |
               Array(Type) |
               Hash(String, Type)

  def initialize(@input : Type)
  end
end

input = {"a" => {"b" => {"c" => {"c1" => 1, "c2" => 2, "c3" => true}}}}
s = Types.new(input)

Here is the error I get when running the code above:

$ crystal types.cr

Error in types.cr:16: instantiating 'Types:Class#new(Hash(String, Hash(String, Hash(String, Hash(String, Bool | Int32)))))'

s = Types.new(input)
          ^~~

in types.cr:11: instance variable '@input' of Types must be Types::Type, not Hash(String, Hash(String, Hash(String, Hash(String, Bool | Int32))))

  def initialize(@input : Type)
                 ^~~~~~

Is this possible with Crystal? How should I approach this?

Thanks!


Solution

  • You can do this specifying type of each hash:

    c = {"c1" => 1, "c2" => 2, "c3" => true} of String => Types::Type
    b = {"c" => c} of String => Types::Type
    a = {"b" => b} of String => Types::Type
    
    t = Types.new({"a" => a} of String => Types::Type)
    pp t # => #<Types:0x103085ec0
         #    @input=
         #      {"a" => {"b" => {"c" => {"c1" => 1, "c2" => 2, "c3" => true}}}}>
    

    Another approach is to define and use Hash-like type:

    alias Type = Nil         |
                 Bool        |
                 Int32       |
                 Int64       |
                 Float64     |
                 String      |
                 Array(Type) |
                 Hash(String, Type)
    
    alias TypesHash = Hash(String, Type)
    
    t = TypesHash{
      "a" => TypesHash{
        "b" => TypesHash{
          "c" => TypesHash{
            "c1" => 1, "c2" => 2, "c3" => true,
          },
        },
      },
    }
    
    t                                            # {"a" => {"b" => {"c" => {"c1" => 1, "c2" => 2, "c3" => true}}}}
    t["a"]                                       # {"b" => {"c" => {"c1" => 1, "c2" => 2, "c3" => true}}}
    t["a"].as(TypesHash)["b"]                    # {"c" => {"c1" => 1, "c2" => 2, "c3" => true}}
    t["a"].as(TypesHash)["b"].as(TypesHash)["c"] # {"c1" => 1, "c2" => 2, "c3" => true}
    

    So you can pass it to the constructor just like TypesHash object:

    class Types
      def initialize(@input : TypesHash); end
    end
    
    Types.new t