module ObjectPatch::Pointer
This module contains the code to convert between an JSON pointer path representation and the keys required to traverse an array. It can make use of an a path and evaluate it against a provided (potentially deeply nested) array or hash.
This is mostly compliant with RFC6901, however, a few small exceptions have been made, though they shouldn’t break compatibility with pure implementations.
Public Class Methods
Given an array of keys this will provide a properly escaped JSONPointer path.
@param [Array<String,Fixnum>] ary_path @return [String]
# File lib/object_patch/pointer.rb, line 47 def encode(ary_path) ary_path = Array(ary_path).map { |p| p.is_a?(String) ? escape(p) : p } "/" << ary_path.join("/") end
Escapes reserved characters as defined by RFC6901. This is intended to escape individual segments of the pointer and thus should not be run on an already generated path.
@see [Pointer#unescape] @param [String] str @return [String]
# File lib/object_patch/pointer.rb, line 59 def escape(str) str.gsub(/~|\//, { '~' => '~0', '/' => '~1' }) end
Given a parsed path and an object, get the nested value within the object.
@param [Array<String,Fixnum>] path Key path to traverse to get the value. @param [Hash,Array] obj The document to traverse. @return [Object] The value at the provided path.
# File lib/object_patch/pointer.rb, line 19 def eval(path, obj) path.inject(obj) do |o, p| if o.is_a?(Hash) raise MissingTargetException unless o.keys.include?(p) o[p] elsif o.is_a?(Array) # The last element +1 is technically how this is interpretted. This # will always trigger the index error so it may not be valuable to # set... p = o.size if p == "-1" # Technically a violation of the RFC to allow reverse access to the # array but I'll allow it... raise ObjectOperationOnArrayException unless p.to_s.match(/\A-?\d+\Z/) raise InvalidIndexError unless p.to_i.abs < o.size o[p.to_i] else # We received a Scalar value from the prior iteration... we can't do # anything with this... raise TraverseScalarException end end end
Convert a JSON pointer into an array of keys that can be used to traverse a parsed JSON document.
@param [String] path @return [Array<String,Fixnum>]
# File lib/object_patch/pointer.rb, line 68 def parse(path) # I'm pretty sure this isn't quite valid but it's a holdover from # tenderlove's code. Once the operations are refactored I believe this # won't be necessary. return [""] if path == "/" # Strip off the leading slash path = path.sub(/^\//, '') path.split("/").map { |p| unescape(p) } end
Unescapes any reserved characters within a JSON pointer segment.
@see [Pointer#escape] @param [String] str @return [String]
# File lib/object_patch/pointer.rb, line 83 def unescape(str) str.gsub(/~[01]/, { '~0' => '~', '~1' => '/' }) end
Private Instance Methods
Given an array of keys this will provide a properly escaped JSONPointer path.
@param [Array<String,Fixnum>] ary_path @return [String]
# File lib/object_patch/pointer.rb, line 47 def encode(ary_path) ary_path = Array(ary_path).map { |p| p.is_a?(String) ? escape(p) : p } "/" << ary_path.join("/") end
Escapes reserved characters as defined by RFC6901. This is intended to escape individual segments of the pointer and thus should not be run on an already generated path.
@see [Pointer#unescape] @param [String] str @return [String]
# File lib/object_patch/pointer.rb, line 59 def escape(str) str.gsub(/~|\//, { '~' => '~0', '/' => '~1' }) end
Given a parsed path and an object, get the nested value within the object.
@param [Array<String,Fixnum>] path Key path to traverse to get the value. @param [Hash,Array] obj The document to traverse. @return [Object] The value at the provided path.
# File lib/object_patch/pointer.rb, line 19 def eval(path, obj) path.inject(obj) do |o, p| if o.is_a?(Hash) raise MissingTargetException unless o.keys.include?(p) o[p] elsif o.is_a?(Array) # The last element +1 is technically how this is interpretted. This # will always trigger the index error so it may not be valuable to # set... p = o.size if p == "-1" # Technically a violation of the RFC to allow reverse access to the # array but I'll allow it... raise ObjectOperationOnArrayException unless p.to_s.match(/\A-?\d+\Z/) raise InvalidIndexError unless p.to_i.abs < o.size o[p.to_i] else # We received a Scalar value from the prior iteration... we can't do # anything with this... raise TraverseScalarException end end end
Convert a JSON pointer into an array of keys that can be used to traverse a parsed JSON document.
@param [String] path @return [Array<String,Fixnum>]
# File lib/object_patch/pointer.rb, line 68 def parse(path) # I'm pretty sure this isn't quite valid but it's a holdover from # tenderlove's code. Once the operations are refactored I believe this # won't be necessary. return [""] if path == "/" # Strip off the leading slash path = path.sub(/^\//, '') path.split("/").map { |p| unescape(p) } end
Unescapes any reserved characters within a JSON pointer segment.
@see [Pointer#escape] @param [String] str @return [String]
# File lib/object_patch/pointer.rb, line 83 def unescape(str) str.gsub(/~[01]/, { '~0' => '~', '~1' => '/' }) end