Created
May 17, 2012 05:40
-
-
Save shanna/2716719 to your computer and use it in GitHub Desktop.
JSONPath like access to Oj JSON structs.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'delegate' | |
require 'oj' | |
require 'strscan' | |
# JSONPath like access to Oj JSON structs. | |
# | |
# @note Array access is identical to Array#[] | |
class Oj::Path < SimpleDelegator | |
class Error < StandardError; end | |
class MissingError < Error; end | |
class ParseError < Error; end | |
class TypeError < Error; end | |
attr_reader :root | |
def self.load string, options = {} | |
new Oj.load(string, {symbol_keys: true}.merge(options)) | |
end | |
def initialize json, document = nil | |
@root = document || self | |
super json | |
end | |
def path path, *default | |
scanner = StringScanner.new(path) | |
context = self | |
while not scanner.eos? | |
if token = scanner.scan(/\$/) | |
context = root | |
elsif token = scanner.scan(/\./) | |
# noop, current context | |
elsif token = scanner.scan(/[a-z0-9_]+/i) | |
raise TypeError.new("Attempt to access Hash key :#{token} on a #{context.class} for '#{path}' position #{scanner.pos}.") \ | |
unless context.respond_to?(:key) | |
unless context.key?(token.to_sym) | |
raise MissingError.new("Unknown Hash key :#{token} for '#{path}' position #{scanner.pos}.") if default.empty? | |
context = default.first | |
break | |
end | |
context = context[token.to_sym] | |
elsif token = scanner.scan(/\[([\-0-9](?:(?:(?:\.\.)|(?:\,\s))[\-0-9])?)\]/) | |
raise TypeError.new("Attempt to access Array #{token} on a #{context.class} for '#{path}' postion #{scanner.pos}.") \ | |
unless context.respond_to?(:[]) | |
context = eval("context#{token}") | |
if context.nil? | |
raise MissingError.new("Array access #{path} returned nil for '#{path}' position #{scanner.pos}.") if default.empty? | |
context = default.first | |
break | |
end | |
else | |
raise ParseError.new("Invalid token '#{scanner.peek(1)}' for '#{path}' position #{scanner.pos}.") | |
end | |
end | |
self.class.new(context, root) | |
end | |
end # Oj::Path |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment