Convert a Hash to a Struct-like object
Tags: ruby, rails, gem
Published: 2022-02-21 08:40:25

## **TL;DR**

Stop thinking about symbols *vs* strings when accessing values in your hash. Simply call a method. Convert your hash to a struct-like object with [hash_to_struct](https://github.com/a-bohush/hash_to_struct) gem. It is built on top of standard `Struct` and `OpenStruct`, with the ability to handle nested hashes/arrays and a few more convenient features.

## Motivation

Working with a ruby `Hash` can sometimes be tricky. Hash's ability to hold almost any object as a key may be a source of frustration when using strings and symbols as hash keys.

Often, when we use a hash to represent some data, we also have to pick `String` or `Symbol` type for the hash keys while in fact we do not really care about the key type. From a human perspective keys `'foo'` and `:foo` in a hash should point to the same value.

And while it may not matter to you, it certainly does for a ruby interpreter. So it is common to get a `nil` in the place where you are expecting `hash[:foo]` to return a value but the hash happen to be `{'foo' => value}` for some reason.

This typically happens around hash serialization actions. For example, at one point you had a hash with symbol keys and saved it to Redis, then later got the hash back from Redis, but keys are strings now.

There is an Active Support Core Extensions class called `ActiveSupport::HashWithIndifferentAccess`, solving precisely this problem. It extends a ruby `Hash`, making it handle keys like `'foo'` and `:foo` as if they were the same.

However, I would not consider this as a go-to approach whenever you don't want to care about a key type. I think the best case for the `ActiveSupport::HashWithIndifferentAccess` is when you work with an API that explicitly expects a hash, but you don't know or don't care what key type the API relies on.

When working with regular serializable data I prefer to use structs (e.g., `Struct`, `OpenStruct`). Unlike hashes, structs represent data in the form of value objects, which is more convenient to me, because I do not have to rely on hash specifics like brackets (`[]`) as accessor method with the whole string vs symbols dilemma. I expect my data to be an object with values accessible through regular method calls, so I can rely on duck typing if needed.

Ruby's standard struct classes are a good fit for this case, but you will need some extra "glue" to make this all work together. There is no easy way to convert `Hash` to `Struct` or `OpenStruct`. While `OpenStrcut` does accept a hash as a constructor argument, `Struct` expects a list of keys first to be mapped to values later. And both `Struct` and `OpenStruct` are not able to perform recursive conversion of a nested hash.

To facilitate this, I've built the [hash_to_struct](https://github.com/a-bohush/hash_to_struct) gem, which defines a unified interface for creating objects based on `Struct` or `OpenStruct` out of `Hash` objects, with an ability to use nested hashes and a few other convenient features. It is a simple gem under 100 lines of code, with no external dependencies.

With this library you can, for example, create a Struct-based value object like this:

```ruby
struct = HashToStruct.struct({q: 1, w: { e: 2 }})
struct.q.w.e # => 2
```
Check out the full API description [here](https://github.com/a-bohush/hash_to_struct/blob/main/README.md).