Hash Comparison
Hashes are checked to ensure their keys and values match the shape.
value = {a: 1}
shape = {a: Integer}
Kt.compare(value:, shape:).match? # => true
By default, no extra or missing hash keys are allowed.
# This will fail because `:b` is not in the shape
value = {a: 1, b: 2}
shape = {a: Integer}
Kt.compare(value:, shape:).match? # => false
# This will fail because `:b` is missing from the value
value = {a: 1}
shape = {a: Integer, b: String}
Kt.compare(value:, shape:).match? # => false
Extra Keys
If you want to allow extra keys, no special syntax is needed. Ruby comes to the rescue!
Ruby accepts more than just strings and symbols as hash keys.
We take advantage of this by applying the same comparison logic to the keys as we do to the values.
value = {a: 1, b: 2, c: 3}
shape = {a: Integer, Symbol => Integer}
Kt.compare(value:, shape:).match? # => true
We've made sure that if you go through the trouble of describing an exact key, it will override more "generic" matches.
value = {a: 'a', b: 'b', c: 'c'}
shape = {a: 'foo', Symbol => String}
Kt.compare(value:, shape:).match? # => false
We consider a shape that defines a case-equality operator (===
) to be a generic matcher.
Key Shapes
All the key matching logic is the same as for values, you can use any shape you like for the keys.
value = { "123e4567-e89b-12d3-a456-426614174000" => "My Id" }
shape = { :$uuid => String}
Kt.compare(value:, shape:).match? # => true
While you can use a shape for a key, for simplicity it's usually best to stick to simple shapes.
Optional Keys
For making keys optional, we provide a special :$undefined
shape.
value = {a: 1}
shape = {a: Integer, b: Kt.any_of(Integer, :$undefined)}
Kt.compare(value:, shape:).match? # => true
This can also be used to disallow a key even when allowing extra keys.
value = {a: 1, b: 2, secret: 'shh'}
shape = {Symbol => Object, secret: :$undefined}
Kt.compare(value:, shape:).match? # => false
Nested Hashes
As with arrays, hashes can be nested as deep as you like.
value = {a: {b: {c: 1}}}
shape = {a: {b: {c: Integer}}}
Kt.compare(value:, shape:).match? # => true
If you're looking for a more in-depth overview of how hash comparison works, check out the Hash Comparison Design Document document.