Class: JSI::JSON::Node

Inherits:
Object
  • Object
show all
Includes:
FingerprintHash, PathedNode
Defined in:
lib/jsi/json/node.rb

Overview

JSI::JSON::Node is an abstraction of a node within a JSON document. it aims to act like the underlying data type of the node's content (generally Hash or Array-like) in most cases.

the main advantage offered by using a Node over the underlying data is in dereferencing. if a Node consists of a hash with a $ref property pointing within the same document, then the Node will transparently follow the ref and return the referenced data.

in most other respects, a Node aims to act like a Hash when the content is Hash-like, an Array when the content is Array-like. methods of Hash and Array are defined and delegated to the node's content.

however, destructive methods are for the most part not implemented. at the moment only #[]= is implemented. since Node thinly wraps the underlying data, you can change the data and it will be reflected in the node. implementations of destructive methods are planned.

methods that return a modified copy such as #merge are defined, and return a copy of the document with the content of the node modified. the original node's document and content are untouched.

Direct Known Subclasses

ArrayNode, HashNode

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from FingerprintHash

#==, #hash

Methods included from PathedNode

#node_content, #node_ptr_deref

Constructor Details

#initialize(document, pointer) ⇒ Node

a Node represents the content of a document at a given pointer.



47
48
49
50
51
52
53
54
55
56
# File 'lib/jsi/json/node.rb', line 47

def initialize(document, pointer)
  unless pointer.is_a?(JSI::JSON::Pointer)
    raise(TypeError, "pointer must be a JSI::JSON::Pointer. got: #{pointer.pretty_inspect.chomp} (#{pointer.class})")
  end
  if document.is_a?(JSI::JSON::Node)
    raise(TypeError, "document of a Node should not be another JSI::JSON::Node: #{document.inspect}")
  end
  @document = document
  @pointer = pointer
end

Instance Attribute Details

#documentObject (readonly) Also known as: node_document

the document containing this Node at our pointer



59
60
61
# File 'lib/jsi/json/node.rb', line 59

def document
  @document
end

#pointerObject (readonly) Also known as: node_ptr

JSI::JSON::Pointer pointing to this node within its document



62
63
64
# File 'lib/jsi/json/node.rb', line 62

def pointer
  @pointer
end

Class Method Details

.new_by_type(document, pointer) ⇒ Object

if the content of the document at the given pointer is Hash-like, returns a HashNode; if Array-like, returns ArrayNode. otherwise returns a regular Node, although Nodes are for the most part instantiated from Hash or Array-like content.



35
36
37
38
39
40
41
42
43
44
# File 'lib/jsi/json/node.rb', line 35

def self.new_by_type(document, pointer)
  content = pointer.evaluate(document)
  if content.respond_to?(:to_hash)
    HashNode.new(document, pointer)
  elsif content.respond_to?(:to_ary)
    ArrayNode.new(document, pointer)
  else
    Node.new(document, pointer)
  end
end

.new_doc(document) ⇒ Object



27
28
29
# File 'lib/jsi/json/node.rb', line 27

def self.new_doc(document)
  new_by_type(document, JSI::JSON::Pointer.new([]))
end

Instance Method Details

#[](subscript) ⇒ Object

returns content at the given subscript - call this the subcontent.

if the content cannot be subscripted, raises TypeError.

if the subcontent is Hash-like, it is wrapped as a JSI::JSON::HashNode before being returned. if the subcontent is Array-like, it is wrapped as a JSI::JSON::ArrayNode before being returned.

if this node's content is a $ref - that is, a hash with a $ref attribute - and the subscript is not a key of the hash, then the $ref is followed before returning the subcontent.



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/jsi/json/node.rb', line 84

def [](subscript)
  ptr = self.pointer
  content = self.content
  if content.respond_to?(:to_hash) && !(content.respond_to?(:key?) ? content : content.to_hash).key?(subscript)
    pointer.deref(document) do |deref_ptr|
      ptr = deref_ptr
      content = ptr.evaluate(document)
    end
  end
  unless content.respond_to?(:[])
    if content.respond_to?(:to_hash)
      content = content.to_hash
    elsif content.respond_to?(:to_ary)
      content = content.to_ary
    else
      raise(NoMethodError, "undefined method `[]`\nsubscripting with #{subscript.pretty_inspect.chomp} (#{subscript.class}) from #{content.class.inspect}. content is: #{content.pretty_inspect.chomp}")
    end
  end
  begin
    subcontent = content[subscript]
  rescue TypeError => e
    raise(e.class, e.message + "\nsubscripting with #{subscript.pretty_inspect.chomp} (#{subscript.class}) from #{content.class.inspect}. content is: #{content.pretty_inspect.chomp}", e.backtrace)
  end
  if subcontent.respond_to?(:to_hash)
    HashNode.new(document, ptr[subscript])
  elsif subcontent.respond_to?(:to_ary)
    ArrayNode.new(document, ptr[subscript])
  else
    subcontent
  end
end

#[]=(subscript, value) ⇒ Object

assigns the given subscript of the content to the given value. the document is modified in place.



117
118
119
120
121
122
123
# File 'lib/jsi/json/node.rb', line 117

def []=(subscript, value)
  if value.is_a?(Node)
    content[subscript] = value.content
  else
    content[subscript] = value
  end
end

#as_json(*opt) ⇒ Object

returns a jsonifiable representation of this node's content



170
171
172
# File 'lib/jsi/json/node.rb', line 170

def as_json(*opt)
  Typelike.as_json(content, *opt)
end

#deref {|Node| ... } ⇒ JSI::JSON::Node

returns a Node, dereferencing a $ref attribute if possible. if this node is not hash-like, does not have a $ref, or if what its $ref cannot be found, this node is returned.

currently only $refs pointing within the same document are followed.

Yields:

  • (Node)

    if a block is given (optional), this will yield a deref'd node. if this node is not a $ref object, the block is not called. if we are a $ref which cannot be followed (e.g. a $ref to an external document, which is not yet supported), the block is not called.

Returns:



134
135
136
137
138
139
# File 'lib/jsi/json/node.rb', line 134

def deref(&block)
  pointer.deref(document) do |deref_ptr|
    return Node.new_by_type(document, deref_ptr).tap(&(block || Util::NOOP))
  end
  return self
end

#document_nodeObject Also known as: document_root_node

a Node at the root of the document



142
143
144
# File 'lib/jsi/json/node.rb', line 142

def document_node
  Node.new_doc(document)
end

#dupObject



180
181
182
# File 'lib/jsi/json/node.rb', line 180

def dup
  modified_copy(&:dup)
end

#fingerprintObject

fingerprint for equality (see FingerprintHash). two nodes are equal if they are both nodes (regardless of type, e.g. one may be a Node and the other may be a HashNode) within equal documents at equal pointers. note that this means two nodes with the same content may not be considered equal.



216
217
218
# File 'lib/jsi/json/node.rb', line 216

def fingerprint
  {class: JSI::JSON::Node, document: document, pointer: pointer}
end

#fragmentObject

the pointer fragment to this node within the document, per RFC 6901 https://tools.ietf.org/html/rfc6901



165
166
167
# File 'lib/jsi/json/node.rb', line 165

def fragment
  pointer.fragment
end

#inspectObject

a string representing this node



193
194
195
# File 'lib/jsi/json/node.rb', line 193

def inspect
  "\#<#{self.class.inspect}#{JSI.object_group_str(object_group_text)} #{node_content.inspect}>"
end

#modified_copy(&block) ⇒ Object

takes a block. the block is yielded the content of this node. the block MUST return a modified copy of that content (and NOT modify the object it is given).



176
177
178
# File 'lib/jsi/json/node.rb', line 176

def modified_copy(&block)
  Node.new_by_type(pointer.modified_document_copy(document, &block), pointer)
end

#object_group_textArray<String>

meta-information about the object, outside the content. used by #inspect / #pretty_print

Returns:

  • (Array<String>)


186
187
188
189
190
# File 'lib/jsi/json/node.rb', line 186

def object_group_text
  [
    "fragment=#{node_ptr.fragment.inspect}",
  ] + (node_content.respond_to?(:object_group_text) ? node_content.object_group_text : [])
end

#parent_nodeObject

the parent of this node. if this node is the document root, raises JSI::JSON::Pointer::ReferenceError.



155
156
157
# File 'lib/jsi/json/node.rb', line 155

def parent_node
  Node.new_by_type(document, pointer.parent)
end

#pathArray<Object>

Returns the path of this node; an array of reference_tokens of the pointer.

Returns:

  • (Array<Object>)

    the path of this node; an array of reference_tokens of the pointer



65
66
67
# File 'lib/jsi/json/node.rb', line 65

def path
  pointer.reference_tokens
end

#pointer_pathObject

the pointer path to this node within the document, per RFC 6901 https://tools.ietf.org/html/rfc6901



160
161
162
# File 'lib/jsi/json/node.rb', line 160

def pointer_path
  pointer.pointer
end

#pretty_print(q) ⇒ Object

pretty-prints a representation this node to the given printer



198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/jsi/json/node.rb', line 198

def pretty_print(q)
  q.instance_exec(self) do |obj|
    text "\#<#{obj.class.inspect}#{JSI.object_group_str(obj.object_group_text)}"
    group_sub {
      nest(2) {
        breakable ' '
        pp obj.content
      }
    }
    breakable ''
    text '>'
  end
end

#root_node?Boolean

Returns whether this node is the root of its document.

Returns:

  • (Boolean)

    whether this node is the root of its document



149
150
151
# File 'lib/jsi/json/node.rb', line 149

def root_node?
  pointer.root?
end