Skip to content

Instantly share code, notes, and snippets.

@hannahwhy
Created February 25, 2011 17:29
Show Gist options
  • Select an option

  • Save hannahwhy/844147 to your computer and use it in GitHub Desktop.

Select an option

Save hannahwhy/844147 to your computer and use it in GitHub Desktop.
an implementation of JSON Schema's fragment resolution algorithm
require 'uri'
##
# Attempts to resolve a fragment identifier (`identifier`) in a deserialized
# JSON object (`object`).
#
# This method always returns a pair of values. The first value is the
# resolution result, and the second value is the portion of the identifier
# that was successfully resolved. The characteristics of these values
# therefore directly depend on resolution success:
#
# | Resolution | First value | Second value |
# | Success | a JSON value | the full identifier |
# | Failure | :error | the identifier up to the failure point |
#
# @param [String] identifier the fragment identifier
# @param [Hash] object a deserialized JSON object
# @return [Array<[Object, :error], String>] the resolution result and
# resolved fragment
# @see http://tools.ietf.org/html/draft-zyp-json-schema-03#section-6.2
# JSON Schema, draft 03, section 6.2
def resolve(identifier, object)
fragment, path = identifier.split('/').inject([object, []]) do |(fragment, path), component|
if component == '#'
[object, path << '#']
else
decoded = URI.decode(component)
next_fragment = case fragment
when Hash
fragment.has_key?(decoded) ? fragment[decoded] : :error
when Array
index = decoded.to_i
if decoded =~ /\d+/ && index >= 0 && index < fragment.length
fragment[index]
else
:error
end
else
:error
end
if next_fragment == :error
break [:error, path]
else
[next_fragment, path << component]
end
end
end
[fragment, path.join('/')]
end
# ---
# demo
# ---
require 'json'
require 'yaml' # better pretty-printing
object = JSON.parse(%Q{
{
"foo": {
"bar": [
{
"baz": {
"qux": 5
}
}
]
},
"metasyntactic names suck": [
"yeah, they do",
"but I lack imagination"
]
}
})
def print(identifier, object)
fragment, path = resolve(identifier, object)
y({ 'expected' => identifier,
'actual' => path,
'fragment' => fragment,
'fragment class' => fragment.class.to_s })
puts
puts '*' * 80
puts
end
print('#', object)
print('#/foo/bar/0/baz/qux', object)
print('#/foo/bar/1/baz/qux', object)
print('#/metasyntactic%20names%20suck/1', object)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment