Skip to content

Commit

Permalink
Compare property default instance locations by id
Browse files Browse the repository at this point in the history
The keys used to collect property defaults are mutable hashes, which
causes weird hash table problems when they're modified (`#hash` no
longer matches even though they have the same `#object_id`).
Explained here: https://docs.ruby-lang.org/en/3.3/Hash.html#class-Hash-label-Modifying+an+Active+Hash+Key

I think this problem got covered up a bit because it requires more than
8 keys for values to be dropped.

`compare_by_identity` fixes things by just using `#object_id` to compare
hash keys. It stays consistent after the hash key is mutated.

From the [docs][0]:

> Note: this requirement does not apply if the Hash uses
> `compare_by_identity` since comparison will then rely on the keys'
> object id instead of `hash` and `eql?`.

Fixes: #179

[0]: https://docs.ruby-lang.org/en/3.3/Hash.html#class-Hash-label-User-Defined+Hash+Keys
  • Loading branch information
davishmcclurg committed Mar 12, 2024
1 parent 9321f1a commit b92e1c2
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 0 deletions.
1 change: 1 addition & 0 deletions lib/json_schemer/result.rb
Expand Up @@ -191,6 +191,7 @@ def classic

def insert_property_defaults(context)
instance_locations = {}
instance_locations.compare_by_identity

results = [[self, true]]
while (result, valid = results.pop)
Expand Down
48 changes: 48 additions & 0 deletions test/hooks_test.rb
Expand Up @@ -668,4 +668,52 @@ def test_insert_property_defaults_ref_depth_first
assert(JSONSchemer.schema(schema, insert_property_defaults: true).valid?(data))
assert_equal('ref', data.fetch('x'))
end

def test_insert_property_defaults_compare_by_identity
data = JSON.parse(%q({
"fieldname": [
{ "aaaa": "item0", "bbbb": "val1", "cccc": true },
{ "aaaa": "item1", "cccc": true, "buggy": true, "dddd": 0 },
{ "aaaa": "item2", "cccc": true, "buggy": true, "dddd": 0 },
{ "aaaa": "item3", "buggy": true },
{ "aaaa": "item4", "cccc": true },
{ "aaaa": "item5", "cccc": true }
]
}))
schema = %q({
"type": "object",
"properties": {
"fieldname": {
"type": "array",
"items": {
"$ref": "#/$defs/Record"
}
}
},
"$defs": {
"Enumerated": {
"enum": [ "val1", "val2" ]
},
"Record": {
"type": "object",
"properties": {
"aaaa": { "type": "string" },
"cccc": { "type": "boolean", "default": false },
"buggy": { "type": "boolean", "default": false },
"bbbb": { "$ref": "#/$defs/Enumerated", "default": "val2" },
"dddd": { "type": "number", "default": 0 },
"eeee": { "type": "boolean", "default": false },
"ffff": { "type": "boolean", "default": false }
}
}
}
})
assert(JSONSchemer.schema(schema, insert_property_defaults: true).valid?(data))
assert_equal('val2', data.dig('fieldname', 2, 'bbbb'))
assert_equal(false, data.dig('fieldname', 3, 'cccc'))
assert_equal(false, data.dig('fieldname', 4, 'buggy'))
assert_equal(0, data.dig('fieldname', 0, 'dddd'))
assert_equal(false, data.dig('fieldname', 1, 'eeee'))
assert_equal(false, data.dig('fieldname', 5, 'ffff'))
end
end

0 comments on commit b92e1c2

Please sign in to comment.