How to translate following transformation into the dry-transformer DSL?

Hey,

I can’t figure out how to translate the following transformation into the dry-transformer DSL.

Dry::Transformer::Recursion.t(
    :recursion, Dry::Transformer::Conditional.t(
        :is, ::Hash, Dry::Transformer::HashTransformations.t(
            :map_keys, ->(key) { Hanami.app["inflector"].underscore(key) }
         )
     )
 )[
    {
        :ID => "57d79414-ab95-4887-8fd8-6418f6adb113",
        :Read => true,
        :Subject => "Hello World.",
        :Attachments => [
            {
                :PartID => "2",
                :FileName => "test.pdf",
                :ContentType => "application/pdf",
                :ContentID => "",
                :Size => 307686,
            },
        ],
    }
]

I tried the following, but without success:

class PayloadAdapter < Dry::Transformer::Pipe
    import Dry::Transformer::HashTransformations
    import Dry::Transformer::Recursion
    import Dry::Transformer::Conditional

    define! do
        recursion(is(::Hash, map_keys(->(key) { Hanami.app["inflector"].underscore(key) })))
    end
end

Please can you help?

thanks and best regards

This is happening because all the examples are built around using Dry::Transformer directly, whereas any bare words in the define! block are generating ASTs, not objects.

This is easily the most confusing thing about this library.

Here’s how you would write this as-is:

class PayloadAdapter < Dry::Transformer::Pipe
    import Dry::Transformer::HashTransformations
    import Dry::Transformer::Recursion
    import Dry::Transformer::Conditional

    define! do
        recursion t(:is, Hash, t(:map_keys, ->(key) { Hanami.app["inflector"].underscore(key) }))
    end
end

However, I don’t think the next person who comes along is going to understand what they’re looking at.

Here’s an approach to this that I have taken

module Transform
  class Registry
    extend Dry::Transformer::Registry

    import Dry::Transformer::ArrayTransformations
    import Dry::Transformer::HashTransformations
    import Dry::Transformer::Conditional

    class << self
      def deep_transform_keys(hash, fn)
        hash.each_with_object({}) do |(key, value), result|
          result[fn.(key)] = if value.is_a?(Hash)
            deep_transform_keys(value, fn)
          elsif value.is_a?(Array)
            value.map { deep_transform_keys(_1, fn) }
          else
            value
          end
        end
      end
    end
  end

  class Function < Dry::Transformer::Pipe
    extend Dry::Initializer
  end
end

class PayloadAdapter < Transform::Function
  option :inflector, default: -> { Hanami.app.inflector }

  import :deep_transform_keys, from: Transform::Registry

  define! do
    deep_transform_keys(&:underscore)
  end

  def underscore(key) = inflector.underscore(_1.to_s)
end
1 Like

Hey @alassek,

a little bit late, but I found out, that there’s a bug at your deep_transform_keys-implementation. It will fail i.e. for a hash like this:

{
  "id" => 1,
  "stringArray" => ["hello", "world"]
}

Here’s a fix:

  def deep_transform_keys(value, fn)
    if value.is_a?(Hash)
      value.each_with_object({}) do |(key, value), result|
        result[fn.call(key)] = deep_transform_keys(value, fn)
      end
    elsif value.is_a?(Array)
      value.map { deep_transform_keys(_1, fn) }
    else
      value
    end
  end

If you are using this in any code base, maybe this message will save you from a bugfix in the future.

best regards