Class: JSI::JSON::Node

Inherits:
Object
  • Object
show all
Includes:
Enumerable, PathedNode, Util::FingerprintHash
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 Util::FingerprintHash

#==, #hash

Methods included from PathedNode

#node_content, #node_ptr_deref

Constructor Details

#initialize(node_document, node_ptr) ⇒ Node

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



50
51
52
53
54
55
56
57
58
59
# File 'lib/jsi/json/node.rb', line 50

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

Instance Attribute Details

#node_documentObject (readonly)

the document containing this Node at our pointer



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

def node_document
  @node_document
end

#node_ptrObject (readonly)

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



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

def node_ptr
  @node_ptr
end

Class Method Details

.new_by_type(node_document, node_ptr) ⇒ 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.



38
39
40
41
42
43
44
45
46
47
# File 'lib/jsi/json/node.rb', line 38

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

.new_doc(node_document) ⇒ Object



30
31
32
# File 'lib/jsi/json/node.rb', line 30

def self.new_doc(node_document)
  new_by_type(node_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.



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/jsi/json/node.rb', line 76

def [](subscript)
  ptr = self.node_ptr
  content = self.node_content
  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(node_document, ptr[subscript])
  elsif subcontent.respond_to?(:to_ary)
    ArrayNode.new(node_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.



103
104
105
106
107
108
109
# File 'lib/jsi/json/node.rb', line 103

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

#as_json(*opt) ⇒ Object

returns a jsonifiable representation of this node's content



139
140
141
# File 'lib/jsi/json/node.rb', line 139

def as_json(*opt)
  Typelike.as_json(node_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:



120
121
122
123
124
125
# File 'lib/jsi/json/node.rb', line 120

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

#document_root_nodeObject

a Node at the root of the document



128
129
130
# File 'lib/jsi/json/node.rb', line 128

def document_root_node
  Node.new_doc(node_document)
end

#dupObject



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

def dup
  modified_copy(&:dup)
end

#inspectObject

a string representing this node



163
164
165
# File 'lib/jsi/json/node.rb', line 163

def inspect
  "\#<#{object_group_text.join(' ')} #{node_content.inspect}>"
end

#jsi_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.



185
186
187
# File 'lib/jsi/json/node.rb', line 185

def jsi_fingerprint
  {class: JSI::JSON::Node, node_document: node_document, node_ptr: node_ptr}
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).



145
146
147
# File 'lib/jsi/json/node.rb', line 145

def modified_copy(&block)
  Node.new_by_type(node_ptr.modified_document_copy(node_document, &block), node_ptr)
end

#object_group_textArray<String>

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

Returns:

  • (Array<String>)


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

def object_group_text
  [
    self.class.inspect,
    node_ptr.uri.to_s,
  ] + (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.



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

def parent_node
  Node.new_by_type(node_document, node_ptr.parent)
end

#pretty_print(q) ⇒ Object

pretty-prints a representation this node to the given printer



168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/jsi/json/node.rb', line 168

def pretty_print(q)
  q.text '#<'
  q.text object_group_text.join(' ')
  q.group_sub {
    q.nest(2) {
      q.breakable ' '
      q.pp node_content
    }
  }
  q.breakable ''
  q.text '>'
end