The Crystal Programming Language Forum

How do I create a nested Hash type?

The following doesn’t compile:

class H < Hash(String, H)
end

p H.new

What’s the use case here? You don’t really need to create your own class as Hash already supports nested types.

class MyClass
  property name : String = "Jim"
end

hash = Hash(String, Hash(Symbol | String, Int32 | Bool | MyClass)).new

hash["foo"] = {:bar => 123, :far => true, "klass" => MyClass.new}

hash # => {"foo" => {:bar => 123, :far => true, "klass" => #<MyClass:0x55e340f73f00 @name="Jim">}}
1 Like

I need it nested to N depth. N is only known at runtime based on the data loaded.
Each Hash at each nested depth has additional properties in addition to the Hash itself.
There are also additional methods I didn’t include in the example that should not exist on the Hash class.

I mean i suppose you could do like:

class CustomHash(K, V) < Hash(String, V)
end

CustomHash{"foo" => "bar"}

Although this would of course include the methods from Hash. I’m assuming its not possible to new up objects instead of using hashes? Might be harder to work with as the types of everything is just going to be a big union.

Another option would be defining a recursive alias, then use that as the type restriction on your own class.

alias MyCustomHash = ...

class MyClass
  def initialize(@hash : MyCustomHash); end
end

But a more OO approach would be better imo.

You can use a recursive alias. But I think you don’t need to use hashes in your program.

Its easier if you tell us what you want to do, what’s your problem (not your solution).

You may also take a look at what’s going on with JSON and YAML, that both give you an arbitrary tree in runtime.

Parsing a sysctl like structure with additional data.

foo.bar.int = 1
foo.bar.str = "foo"
foo.bar.baz.int = 1

I need dig like functions and iteration at any point. A nested set of hashes with additional properties seemed like the closest data structure.

hash.dig["foo", "bar"].each

You can always do something like this: https://play.crystal-lang.org/#/r/74qu

3 Likes

Yes, that’s what JSON::Any#dig does (https://crystal-lang.org/api/0.29.0/JSON/Any.html#dig(key%3AString|Int%2C*subkeys)-instance-method)

The problem may arise if you want a key to have a value and a subtree at the same time:

foo.bar = "value"
foo.bar.baz = "another value"

One way around would be a notion of an empty key, so that becomes something like

foo.bar = Subtree.new
foo.bar.<empty> = "value"
foo.bar.baz = "another value"

Also, an often overlooked way to solve this is to flatten the tree into a single level hash, bash-style:

tree["foo.bar.int"] = 1
tree["foo.bar.str"] = "foo"
tree["foo.bar.baz.int"] = 1

As long as you are fine with the drawbacks it’s perfectly legitimate.

2 Likes

Got it. Thank you.

Example: https://play.crystal-lang.org/#/r/74wq

test = Hash(String, Hash(String, Hash(String, String))).new

test["test"] = {"one" => {"one" => "test"}}

pp test

Just replace Hash(String, String) with the last String, dependent on how many you want nested.

This is the first time Didactic.Drunk has posted — let’s welcome them to our community!

By the way, welcome to the community!! :smiley:

In Perl language code, nested Hash is very easily to create and use,like this:

#!/usr/bin/perl -w

my %h;
$h{"A"}{"B"}{"C"} = 1; 
# this is depth=3 netst hash, can use any N depth hash easily in Perl.

So sometimes I also want to use nested Hash Type easily in Crystal.

it do works! thanks~