class StructureMatch
StructureMatch
encapsulates all the logic needed perform deep binding and scoring of a JSON data structure.
Public Class Methods
StructureMatchers are initialized by passing in an example nested hash that maps out what StructureMatch
should dig through, bind matching values, and how to score the matches it found. As an example:
StructureMatch.new("foo" => "bar", "numbermatch" => 5, "regexmatch" => { "__sm_leaf" => true, "op"=> "regex", "match" => "foo(bar)"}, "ormatch" => { "__sm_leaf" => true, "op" => "or", "match" => [ { "op" => ">", "match" => 7 }, { "op" => "<", "match" => 10}]})
will create a matcher that perfectly matches the following JSON:
{ "foo": "bar", "numbermatch": 5, "regexmatch": "foobar", "ormatch": 8 }
This match will be assigned a score of 5.
# File lib/structurematch.rb, line 127 def initialize(matcher) raise "#{matcher.inspect} must be a Hash" unless matcher.kind_of?(Hash) @matchers = Hash.new matcher.each do |k,v| raise "#{k} must be a String" unless k.kind_of?(String) key = k.dup.freeze @matchers[key] = case when v.kind_of?(TrueClass) || v.kind_of?(FalseClass) || v.kind_of?(NilClass) || v.kind_of?(Numeric) Comparator.new("op" => "==", "match" => v) when v.kind_of?(String) then Comparator.new("op" => "==", "match" => v.dup.freeze) when v.kind_of?(Array) then Comparator.new("op" => "member", "match" => v.dup.freeze) when v.kind_of?(Hash) then !!v["__sm_leaf"] ? Comparator.new(v) : StructureMatch.new(v) else raise "Cannot handle node type #{v.inspect}" end end end
Public Instance Methods
Takes a (possibly nested) hash that is a result of parsing JSON and matches what it can, assigning a score in the process.
Returns a two-entry array in the form of [binds,score]:
- binds
-
The nested hash corresponding to the matched values from val.
- score
-
The score assigned to this match.
# File lib/structurematch.rb, line 154 def bind(val) raise "Must pass a Hash to StructureMatch.bind" unless val.kind_of?(Hash) score = 0 binds = Hash.new @matchers.each do |k,v| offset = 0 res = nil case when !val.has_key?(k) then offset = -1 when v.kind_of?(Comparator) offset,res = v.test(val[k]) binds[k] = res if offset > 0 when v.kind_of?(StructureMatch) res,offset = v.bind(val[k]) binds[k] = res unless res.empty? else raise "StructureMatch.bind: No idea how to handle #{v.class.name}: #{v.inspect}" end score += offset end [binds,score] end
As bind, but transforms the nested hash into a flat hash with nested keys joined by sep.
# File lib/structurematch.rb, line 179 def flatbind(val, sep='.') binds,score = bind(val) target = Hash.new prefix = '' _flatbind = lambda do |vals,p| vals.each do |k,v| key = p.empty? ? k : "#{p}#{sep}#{k.to_s}" if v.kind_of?(Hash) _flatbind.call(v,key) else target[key] = v end end end _flatbind.call(binds,"") [target,score] end
Runs bind on val and returns just the score component.
# File lib/structurematch.rb, line 198 def score(val) bind(val)[1] end