Best approach to unwrapping multi-level hashes

The is a question about Transproc, though I hope to incorporate it into some work with dry-rb libraries as well. If this is the wrong place for the question, I’d be happy to repost elsewhere.

What is the best way to unwrap a multi-level nested hash? Say, for an arbitrary working example, I have an api that provides children’s answers to “what do you want to be when you grow up?”. Let’s say the data from the api looks something like this:

[
  {
    name: "Sally",
    current_interest: {
      level: 8,
      summary: {
        name: "NASA Engineer"
      }
    }
  },
  {
    name: "Bob",
    current_interest: {
      level: 6,
      summary: {
        name: "Data analyst" # every kid's dream :)
      }
    }
  }
]

For the purpose of another app, the data needs to be mapped to:

[
  {
    name: "Sally",
    interest_level: 8,
    interest_name: "NASA Engineer"
  },
  {
    name: "Sally",
    interest_level: 6,
    interest_name: "Data analyst"
  }
]

I want to unwrap :current_interest and it’s :summary into the top-level hash and rename it’s keys. My current implementation is:

require "spec_helper"

RSpec.describe "Unwrapping multi-level nested hash" do
  let(:input) do
    [
      {
        name: "Sally",
        current_interest: {
          level: 8,
          summary: {
            name: "NASA Engineer"
          }
        }
      },
      {
        name: "Bob",
        current_interest: {
          level: 6,
          summary: {
            name: "Data analyst" # every kid's dream :)
          }
        }
      }
    ]
  end

  let(:output) do
    [
      {
        name: "Sally",
        interest_level: 8,
        interest_name: "NASA Engineer"
      },
      {
        name: "Sally",
        interest_level: 6,
        interest_name: "Data analyst"
      }
    ]
  end

  T = Module.new do
    extend Transproc::Registry
    import Transproc::HashTransformations
  end

  specify do
    step_one = T[:unwrap, :current_interest, [:level, :summary], prefix: true]
    step_two = T[:unwrap, :current_interest_summary, [:name], prefix: true]
    step_three = T[
      :rename_keys,
      current_interest_level: :interest_level,
      current_interest_summary_name: :interest_name,
    ]

    expect(
      (step_one >> step_two >> step_three).call(input[0])
    ).to eql(output[0])
  end
end

Is there a better approach? Is there a transformation that can more directly unwrap the multi-level nesting? Or is multiple transformation one step at a time the right way to go?

This is fine. In fact, I will be moving transproc to dry-transformer later this year.

There’s no built-in transformation for such a use case so your approach is OK. We can easily add new transformations that are built on top of simpler ones, so maybe we should add one to help in cases like yours :thinking:

@solnic Thanks for the reply. I’ll continue down this pathway. Thanks for all the great work in Transproc and dry-rb!

1 Like