# How can I convert a string into an array of hashes?

**URL:** <https://discourse.dry-rb.org/t/how-can-i-convert-a-string-into-an-array-of-hashes/819>\
**Category:** dry-validation\
**Created:** [July 1, 2019, 6:35pm UTC](https://discourse.dry-rb.org/t/how-can-i-convert-a-string-into-an-array-of-hashes/819 "2019-07-01T18:35:24Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![shepmaster](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/shepmaster/32/396_2.png) [@shepmaster](https://discourse.dry-rb.org/u/shepmaster)\
**Post date:** [July 1, 2019, 6:35pm UTC](https://discourse.dry-rb.org/t/how-can-i-convert-a-string-into-an-array-of-hashes/819/1 "2019-07-01T18:35:24Z")

</div>

I’m using dry-validations 0.13.3 with Ruby 2.6.2. I’d like to convert a string passed in from our frontend into an array of hashes:

```auto
schema = Dry::Validation.Schema do
  required(:sorts).each do
    schema do
      required(:name).value(included_in?: %i[a b])
      required(:dir).value(included_in?: %i[asc desc])
    end
  end
end

puts schema.call(sorts: 'a:asc,b:desc').inspect

```

This yields the error:

```nohighlight
#<Dry::Validation::Result output={:sorts=>"a:asc,b:desc"} errors={:sorts=>["must be an array"]}>

```

Following the [instructions about input preprocessing](https://dry-rb.org/gems/dry-validation/0.13/input-preprocessing/), I tried creating a custom type:

```auto
SortString =
  Dry::Types::Definition
    .new(Array)
    .constructor do |input|
  input.split(',').map do |s|
    name, dir = s.split(':', 2)
    { name: name.to_sym, dir: dir.to_sym }
  end
end

puts SortString['a:asc,b:desc']

```

This appears to work:

```nohighlight
{:name=>:a, :dir=>:asc}
{:name=>:b, :dir=>:desc}

```

But using it in the validation has the same error:

```auto
schema = Dry::Validation.Schema do
  configure { config.type_specs = true }

  required(:sorts, SortString).each do
    schema do
      required(:name).value(included_in?: %i[a b])
      required(:dir).value(included_in?: %i[asc desc])
    end
  end
end

puts schema.call(sorts: 'a:asc,b:desc').inspect

```

```nohighlight
#<Dry::Validation::Result output={:sorts=>"a:asc,b:desc"} errors={:sorts=>["must be an array"]}>

```

---

<div class="post-metadata">

**Author:** ![flash-gordon](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/flash-gordon/32/494_2.png) [@flash-gordon](https://discourse.dry-rb.org/u/flash-gordon)\
**Post date:** [July 2, 2019, 11:18am UTC](https://discourse.dry-rb.org/t/how-can-i-convert-a-string-into-an-array-of-hashes/819/2 "2019-07-02T11:18:23Z")

</div>

For the record, no idea if it’s possible in 0.x series but dry-schema 1.x can do this:

```ruby
SortString = Types::Array.constructor do |input|
  input.split(',').map do |s|
    name, dir = s.split(':', 2)
    { name: name.to_sym, dir: dir.to_sym }
  end
end

Schema = Dry::Schema.define do
  required(:sorts).value(SortString).each do
    hash do
      required(:name).value(included_in?: %i[a b])
      required(:dir).value(included_in?: %i[asc desc])
    end
  end
end

dry-schema> Schema.(sorts: 'a:asc,b:desc')
=> #<Dry::Schema::Result{:sorts=>[{:name=>:a, :dir=>:asc}, {:name=>:b, :dir=>:desc}]} errors={}>

```

---

<div class="post-metadata">

**Author:** ![shepmaster](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/shepmaster/32/396_2.png) [@shepmaster](https://discourse.dry-rb.org/u/shepmaster)\
**Post date:** [July 8, 2019, 4:01pm UTC](https://discourse.dry-rb.org/t/how-can-i-convert-a-string-into-an-array-of-hashes/819/3 "2019-07-08T16:01:51Z")

</div>

I’m not entirely sure what the difference is, but I did get it to work:

```ruby
# frozen_string_literal: true

require 'dry-validation'

SortString =
  Dry::Types::Definition
    .new(Array)
    .constructor do |input|
  input.split(',').map do |s|
    by, dir = s.split(':', 2)
    { by: by.to_sym, dir: dir.to_sym }
  end
end

VALID_SORTS = %i[a b].freeze
VALID_SORT_DIRECTIONS = %i[asc desc].freeze

RefinementsContract = Dry::Validation.Params do
  configure do
    config.type_specs = true
  end

  optional(:sorts, SortString).each do
    schema do
      required(:by).value(included_in?: VALID_SORTS)
      required(:dir).value(included_in?: VALID_SORT_DIRECTIONS)
    end
  end
end

puts RefinementsContract.call(sorts: 'a:asc,b:desc').inspect

```

---

<div class="post-metadata">

**Author:** ![shepmaster](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/shepmaster/32/396_2.png) [@shepmaster](https://discourse.dry-rb.org/u/shepmaster)\
**Post date:** [July 8, 2019, 6:07pm UTC](https://discourse.dry-rb.org/t/how-can-i-convert-a-string-into-an-array-of-hashes/819/4 "2019-07-08T18:07:24Z")

</div>

> [@flash-gordon](#):
>
> but dry-schema 1.x can do this:

Is there some mapping that needs to be done to transform your example using dry-schema directly to be used in dry-validations? I ran this code:

```ruby
# frozen_string_literal: true

require 'dry-validation'

module Types
  include Dry.Types()
end

SortString = Types.Constructor(Array) do |values|
  values.split(',').map do |s|
    by, dir = s.split(':', 2)
    { by: by.to_sym, dir: dir.to_sym }
  end
end

p SortString['name:asc,date:desc']

class ExampleContract < Dry::Validation::Contract
  VALID_SORTS = %i[name date].freeze
  VALID_SORT_DIRECTIONS = %i[asc desc].freeze

  params do
    optional(:sorts).value(SortString).each do
      hash do
        required(:by).value(included_in?: VALID_SORTS)
        required(:dir).value(included_in?: VALID_SORT_DIRECTIONS)
      end
    end
  end
end

p ExampleContract.new.call('sorts' => 'name:asc,date:desc')

```

But it fails to validate:

````plaintext
[{:by=>:name, :dir=>:asc}, {:by=>:date, :dir=>:desc}]
#<Dry::Validation::Result{:sorts=>"name:asc,date:desc"} errors={:sorts=>["must be an array"]}>```

````

- dry-validation (1.1.1)
- dry-schema (1.2.0)

---

<div class="post-metadata">

**Author:** ![shepmaster](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/shepmaster/32/396_2.png) [@shepmaster](https://discourse.dry-rb.org/u/shepmaster)\
**Post date:** [July 8, 2019, 7:45pm UTC](https://discourse.dry-rb.org/t/how-can-i-convert-a-string-into-an-array-of-hashes/819/5 "2019-07-08T19:45:45Z")

</div>

There’s apparently an important difference between what you used:

```auto
SortString = Types::Array.constructor do |input|

```

And [how it is documented to make a custom type](https://dry-rb.org/gems/dry-types/1.0/custom-types/):

```ruby
SortString = Types.Constructor(Array) do |values|

```

1. Where is the former documented?
2. What is the meaningful difference in the two?
3. Is this a bug that should be filed?

---

<div class="post-metadata">

**Author:** ![flash-gordon](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/flash-gordon/32/494_2.png) [@flash-gordon](https://discourse.dry-rb.org/u/flash-gordon)\
**Post date:** [July 8, 2019, 9:13pm UTC](https://discourse.dry-rb.org/t/how-can-i-convert-a-string-into-an-array-of-hashes/819/6 "2019-07-08T21:13:40Z")

</div>

> [@shepmaster](#):
>
> Is this a bug that should be filed?

I think so. But it’s not clear where. I’ll take another look.

---

<div class="post-metadata">

**Author:** ![shepmaster](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/shepmaster/32/396_2.png) [@shepmaster](https://discourse.dry-rb.org/u/shepmaster)\
**Post date:** [July 9, 2019, 6:24pm UTC](https://discourse.dry-rb.org/t/how-can-i-convert-a-string-into-an-array-of-hashes/819/7 "2019-07-09T18:24:31Z")

</div>

> [@flash-gordon](#):
>
> I think so.

Thank you. I’ve opened

> <https://github.com/dry-rb/dry-schema/issues/171>
>
> \*\*Describe the bug\*\*
> 
> Types created with \`Types.Constructor(Array)\` behave dif…ferently from those created with \`Types::Array.constructor\` when used with \`.each\`
> 
> \*\*To Reproduce\*\*
> 
> \`\`\`ruby
> \# frozen\_string\_literal: true
> 
> require 'dry-schema'
> 
> module Types
> include Dry.Types()
> end
> 
> SortString = Types.Constructor(Array) do |values|
> values.split(',').map do |s|
> by, dir = s.split(':', 2)
> { by: by.to\_sym, dir: dir.to\_sym }
> end
> end
> 
> schema = Dry::Schema.Params do
> VALID\_SORTS = %i\[name date\].freeze
> VALID\_SORT\_DIRECTIONS = %i\[asc desc\].freeze
> 
> optional(:sorts).value(SortString).each do
> hash do
> required(:by).value(included\_in?: VALID\_SORTS)
> required(:dir).value(included\_in?: VALID\_SORT\_DIRECTIONS)
> end
> end
> end
> 
> puts schema.('sorts' =\> 'name:asc,date:desc').inspect
> \`\`\`
> 
> \*\*Expected behavior\*\*
> 
> \`:sorts\` would be an array of hashes.
> 
> \*\*Actual behavior\*\*
> 
> It fails validation:
> 
> \`\`\`
> \[{:by=\>:name, :dir=\>:asc}, {:by=\>:date, :dir=\>:desc}\]
> \#\<Dry::Schema::Result{:sorts=\>"name:asc,date:desc"} errors={:sorts=\>\["must be an array"\]}\>
> \`\`\`
> 
> \*\*Your environment\*\*
> 
> \- Affects my production application: NO
> \- dry-validation version: 1.1.1
> \- Ruby version: 2.6.2
> \- OS: macOS

---

<div class="post-metadata">

**Author:** ![flash-gordon](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/flash-gordon/32/494_2.png) [@flash-gordon](https://discourse.dry-rb.org/u/flash-gordon)\
**Post date:** [July 9, 2019, 9:27pm UTC](https://discourse.dry-rb.org/t/how-can-i-convert-a-string-into-an-array-of-hashes/819/8 "2019-07-09T21:27:55Z")

</div>

ok, thanks, though it’s 100% not dry-validation 🙂 It’s either dry-schema or dry-types. Also `Types.Constructor(Array)` and `Types::Array.constructor` are different types but in this context I would expect them to work the same way.
