How to validate an array of different objects using dry-schema gem

Hello guys,
Thank you all for your work. Your gems are awesome.

I want to migrate all of my JSON schemas to the dry-validation and the dry-schema but I found out that dry-schema doesn’t have some functionality I need.

Could you check my question on StackOverflow, please?

Not really sure how to make this work with the exact input you want but here is an attempt using schema composition

require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'
  gem 'dry-schema'
end

require 'dry-schema'

One = Dry::Schema.JSON do
  required(:type).filled(:string)
  required(:value).filled(:integer)
end
Two = Dry::Schema.JSON do
  required(:type).filled(:string)
  required(:kind).filled(:string)
end

BaseSchema = Dry::Schema.JSON do
  required(:array).array(:hash) do
    required(:element).hash(One | Two)
  end
end

puts BaseSchema.call(
  {
    "array": [
      {
        "element": {
          "type": 'type_1',
          "value": 5
        }
      },
      {
        "element": {
          "type": 'type_2',
          "kind": 'person'
        }
      },
      {
        "element": {
          "type": 'type_2',
          "value": 'string'
        }
      }
    ]
  }
).errors.to_h.inspect

# {:array=>{2=>{:element=>{:or=>[{:value=>["must be an integer"]}, {:kind=>["is missing"]}]}}}}

The problem here is that i had to put an extra key on the values because i dont know how to validate the root array

required(:element).hash(One | Two)

Validating that exactly one of each schema are present would require a Dry::Validation::Contract

ByValue = Dry::Schema.JSON do
  required(:type).value(:string, eql?: "type_1")
  required(:value).value(:integer)
end

ByKind = Dry::Schema.JSON do
  required(:type).value(:string, eql?: "type_2")
  required(:kind).filled
end

class MyContract < Dry::Validation::Contract
  json do
    required(:array).array do
      schema ByValue | ByKind
    end
  end

  rule :array do
    unless value.count { |v| ByValue.valid?(v) } == 1
      key.failure("must have exactly one ByValue schema")
    end
  end

  rule :array do
    unless value.count { |v| ByKind.valid?(v) } == 1
      key.failure("must have exactly one ByKind schema")
    end
  end
end
1 Like