dry-transformer get a value from hash

I have an input:

{
  "job_status": "SUCCEEDED",
  "expense_documents": [
    {"type": "total"},
    {"type": "tax"}
  ]
}

I want to get:

[{"type": "total"},{"type": "tax"}]

Simplified example, I do more processing, but essentially I can’t find a DSL to get a value.

define! do
  unwrap "expense_documents"
end

Fails, I think it expects hash as a value.

wrong element type Hash at 0 (expected array)
dry/transformer/hash_transformations.rb:284

Am I missing something or use it in a wrong way?

If you look at the API documentation, you’ll see that unwrap has a signature of .unwrap(source_hash, root, selected = nil, prefix: false) ⇒ Hash. So it assumes that it is merging the nested key into the top-level.

Scanning through the list looking at the type signatures, none of the existing transformations perform this kind of type transformation in the way you need.

In this situation, you can easily add your own transform method.

Easiest and most direct way is just define a method in the transformer object

#!/usr/bin/env ruby

require "bundler/inline"

gemfile do
  source "https://rubygems.org"
  gem "dry-transformer"
end

require "dry/transformer"

class JobMapper < Dry::Transformer::Pipe
  import Dry::Transformer::HashTransformations

  define! do
    extract_expenses
  end

  def extract_expenses(job)
    job.fetch(:expense_documents)
  end
end

puts JobMapper.new.({
  job_status: "SUCCEEDED",
  expense_documents: [
    { type: "total" },
    { type: "tax" }
  ]
}).inspect

Eventually you’ll want to share custom methods with multiple transformers, you can do that with a registry.

#!/usr/bin/env ruby

require "bundler/inline"

gemfile do
  source "https://rubygems.org"
  gem "dry-transformer"
end

require "dry/transformer"

module MyRegistry
  extend Dry::Transformer::Registry

  import Dry::Transformer::HashTransformations

  register :map_subkey do |hash, key|
    hash.fetch(key).then { Array(_1) }
  end
end

class JobMapper < Dry::Transformer::Pipe
  import MyRegistry

  define! do
    map_subkey :expense_documents
  end
end

puts JobMapper.new.({
  job_status: "SUCCEEDED",
  expense_documents: [
    { type: "total" },
    { type: "tax" }
  ]
}).inspect
2 Likes