Module: ObjectPatch::Pointer

Defined in:
lib/object_patch/pointer.rb

Overview

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.

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.encode(ary_path) ⇒ String

Given an array of keys this will provide a properly escaped JSONPointer path.

Parameters:

  • ary_path (Array<String,Fixnum>)

Returns:

  • (String)


47
48
49
50
# 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

.escape(str) ⇒ String

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.

Parameters:

  • str (String)

Returns:

  • (String)

See Also:

  • [Pointer[Pointer#unescape]


59
60
61
# File 'lib/object_patch/pointer.rb', line 59

def escape(str)
  str.gsub(/~|\//, { '~' => '~0', '/' => '~1' })
end

.eval(path, obj) ⇒ Object

Given a parsed path and an object, get the nested value within the object.

Parameters:

  • path (Array<String,Fixnum>)

    Key path to traverse to get the value.

  • obj (Hash, Array)

    The document to traverse.

Returns:

  • (Object)

    The value at the provided path.



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 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

.parse(path) ⇒ Array<String,Fixnum>

Convert a JSON pointer into an array of keys that can be used to traverse a parsed JSON document.

Parameters:

  • path (String)

Returns:

  • (Array<String,Fixnum>)


68
69
70
71
72
73
74
75
76
# 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

.unescape(str) ⇒ String

Unescapes any reserved characters within a JSON pointer segment.

Parameters:

  • str (String)

Returns:

  • (String)

See Also:

  • [Pointer[Pointer#escape]


83
84
85
# File 'lib/object_patch/pointer.rb', line 83

def unescape(str)
  str.gsub(/~[01]/, { '~0' => '~', '~1' => '/' })
end

Instance Method Details

#encode(ary_path) ⇒ String (private)

Given an array of keys this will provide a properly escaped JSONPointer path.

Parameters:

  • ary_path (Array<String,Fixnum>)

Returns:

  • (String)


47
48
49
50
# 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

#escape(str) ⇒ String (private)

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.

Parameters:

  • str (String)

Returns:

  • (String)

See Also:

  • ObjectPatch::Pointer.[Pointer[Pointer#unescape]


59
60
61
# File 'lib/object_patch/pointer.rb', line 59

def escape(str)
  str.gsub(/~|\//, { '~' => '~0', '/' => '~1' })
end

#eval(path, obj) ⇒ Object (private)

Given a parsed path and an object, get the nested value within the object.

Parameters:

  • path (Array<String,Fixnum>)

    Key path to traverse to get the value.

  • obj (Hash, Array)

    The document to traverse.

Returns:

  • (Object)

    The value at the provided path.



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 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

#parse(path) ⇒ Array<String,Fixnum> (private)

Convert a JSON pointer into an array of keys that can be used to traverse a parsed JSON document.

Parameters:

  • path (String)

Returns:

  • (Array<String,Fixnum>)


68
69
70
71
72
73
74
75
76
# 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

#unescape(str) ⇒ String (private)

Unescapes any reserved characters within a JSON pointer segment.

Parameters:

  • str (String)

Returns:

  • (String)

See Also:

  • ObjectPatch::Pointer.[Pointer[Pointer#escape]


83
84
85
# File 'lib/object_patch/pointer.rb', line 83

def unescape(str)
  str.gsub(/~[01]/, { '~0' => '~', '~1' => '/' })
end