Class: Scorpio::JSON::Node
- Inherits:
-
Object
- Object
- Scorpio::JSON::Node
- Includes:
- FingerprintHash
- Defined in:
- lib/scorpio/json/node.rb
Overview
Scorpio::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 (Hash or Array, generally) in most cases, defining methods of Hash and Array which delegate to the content. However, destructive methods are not defined, as modifying the content of a node would change it for any other nodes in the document that contain or refer to it.
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.
Instance Attribute Summary collapse
-
#document ⇒ Object
readonly
the document containing this Node at is path.
-
#path ⇒ Object
readonly
the path of this Node within its document.
-
#pointer ⇒ Object
readonly
::JSON::Schema::Pointer representing the path to this node within its document.
Class Method Summary collapse
-
.new_by_type(document, path) ⇒ Object
if the content of the document at the given path is a Hash, returns a HashNode; if an Array, returns ArrayNode.
Instance Method Summary collapse
- #[](k) ⇒ Object
- #as_json ⇒ Object
-
#content ⇒ Object
the raw content of this Node from the underlying document at this Node’s path.
- #deref ⇒ Object
-
#document_node ⇒ Object
a Node at the root of the document.
- #fingerprint ⇒ Object
-
#fragment ⇒ Object
the pointer fragment to this node within the document, per RFC 6901 tools.ietf.org/html/rfc6901.
-
#initialize(document, path) ⇒ Node
constructor
a Node represents the content of a document at a given path.
- #inspect ⇒ Object
-
#modified_copy ⇒ Object
takes a block.
- #object_group_text ⇒ Object
-
#parent_node ⇒ Object
the parent of this node.
-
#pointer_path ⇒ Object
the pointer path to this node within the document, per RFC 6901 tools.ietf.org/html/rfc6901.
- #pretty_print(q) ⇒ Object
Methods included from FingerprintHash
Constructor Details
#initialize(document, path) ⇒ Node
a Node represents the content of a document at a given path.
31 32 33 34 35 36 |
# File 'lib/scorpio/json/node.rb', line 31 def initialize(document, path) raise(ArgumentError, "path must be an array. got: #{path.pretty_inspect.chomp} (#{path.class})") unless path.is_a?(Array) @document = document @path = path.dup.freeze @pointer = ::JSON::Schema::Pointer.new(:reference_tokens, path) end |
Instance Attribute Details
#document ⇒ Object (readonly)
the document containing this Node at is path
41 42 43 |
# File 'lib/scorpio/json/node.rb', line 41 def document @document end |
#path ⇒ Object (readonly)
the path of this Node within its document
39 40 41 |
# File 'lib/scorpio/json/node.rb', line 39 def path @path end |
#pointer ⇒ Object (readonly)
::JSON::Schema::Pointer representing the path to this node within its document
43 44 45 |
# File 'lib/scorpio/json/node.rb', line 43 def pointer @pointer end |
Class Method Details
.new_by_type(document, path) ⇒ Object
if the content of the document at the given path is a Hash, returns a HashNode; if an Array, returns ArrayNode. otherwise returns a regular Node, though, for the most part this will be called with Hash or Array content.
18 19 20 21 22 23 24 25 26 27 28 |
# File 'lib/scorpio/json/node.rb', line 18 def self.new_by_type(document, path) node = Node.new(document, path) content = node.content if content.is_a?(Hash) HashNode.new(document, path) elsif content.is_a?(Array) ArrayNode.new(document, path) else node end end |
Instance Method Details
#[](k) ⇒ Object
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/scorpio/json/node.rb', line 50 def [](k) node = self content = node.content if content.is_a?(Hash) && !content.key?(k) node = node.deref content = node.content end begin el = content[k] rescue TypeError => e raise(e.class, e. + "\nsubscripting with #{k.pretty_inspect.chomp} (#{k.class}) from #{content.class.inspect}. self is: #{pretty_inspect.chomp}", e.backtrace) end if el.is_a?(Hash) || el.is_a?(Array) self.class.new_by_type(node.document, node.path + [k]) else el end end |
#as_json ⇒ Object
117 118 119 |
# File 'lib/scorpio/json/node.rb', line 117 def as_json Typelike.as_json(content) end |
#content ⇒ Object
the raw content of this Node from the underlying document at this Node’s path.
46 47 48 |
# File 'lib/scorpio/json/node.rb', line 46 def content pointer.evaluate(document) end |
#deref ⇒ Object
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/scorpio/json/node.rb', line 69 def deref content = self.content return self unless content.is_a?(Hash) && content['$ref'].is_a?(String) if content['$ref'][/\A#/] return self.class.new_by_type(document, ::JSON::Schema::Pointer.parse_fragment(content['$ref'])).deref end # HAX for how google does refs and ids if document_node['schemas'].respond_to?(:to_hash) if document_node['schemas'][content['$ref']] return document_node['schemas'][content['$ref']] end _, deref_by_id = document_node['schemas'].detect { |_k, schema| schema['id'] == content['$ref'] } if deref_by_id return deref_by_id end end #raise(NotImplementedError, "cannot dereference #{content['$ref']}") # TODO return self end |
#document_node ⇒ Object
a Node at the root of the document
94 95 96 |
# File 'lib/scorpio/json/node.rb', line 94 def document_node Node.new_by_type(document, []) end |
#fingerprint ⇒ Object
189 190 191 |
# File 'lib/scorpio/json/node.rb', line 189 def fingerprint {is_node: self.is_a?(Scorpio::JSON::Node), document: document, path: path} end |
#fragment ⇒ Object
the pointer fragment to this node within the document, per RFC 6901 tools.ietf.org/html/rfc6901
113 114 115 |
# File 'lib/scorpio/json/node.rb', line 113 def fragment pointer.fragment end |
#inspect ⇒ Object
172 173 174 |
# File 'lib/scorpio/json/node.rb', line 172 def inspect "\#<#{self.class.inspect} #{object_group_text} #{content.inspect}>" end |
#modified_copy ⇒ 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).
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
# File 'lib/scorpio/json/node.rb', line 123 def modified_copy # we need to preserve the rest of the document, but modify the content at our path. # # this is actually a bit tricky. we can't modify the original document, obviously. # we could do a deep copy, but that's expensive. instead, we make a copy of each array # or hash in the path above this node. this node's content is modified by the caller, and # that is recursively merged up to the document root. the recursion is done with a # y combinator, for no other reason than that was a fun way to implement it. modified_document = ycomb do |rec| proc do |subdocument, subpath| if subpath == [] yield(subdocument) else car = subpath[0] cdr = subpath[1..-1] if subdocument.respond_to?(:to_hash) car_object = rec.call(subdocument[car], cdr) if car_object.object_id == subdocument[car].object_id subdocument else subdocument.merge({car => car_object}) end elsif subdocument.respond_to?(:to_ary) if car.is_a?(String) && car =~ /\A\d+\z/ car = car.to_i end unless car.is_a?(Integer) raise(TypeError, "bad subscript #{car.pretty_inspect.chomp} with remaining subpath: #{cdr.inspect} for array: #{subdocument.pretty_inspect.chomp}") end car_object = rec.call(subdocument[car], cdr) if car_object.object_id == subdocument[car].object_id subdocument else subdocument.dup.tap do |arr| arr[car] = car_object end end else raise(TypeError, "bad subscript: #{car.pretty_inspect.chomp} with remaining subpath: #{cdr.inspect} for content: #{subdocument.pretty_inspect.chomp}") end end end end.call(document, path) Node.new_by_type(modified_document, path) end |
#object_group_text ⇒ Object
169 170 171 |
# File 'lib/scorpio/json/node.rb', line 169 def object_group_text "fragment=#{fragment.inspect}" end |
#parent_node ⇒ Object
the parent of this node. if this node is the document root (its path is empty), raises ::JSON::Schema::Pointer::ReferenceError.
100 101 102 103 104 105 106 |
# File 'lib/scorpio/json/node.rb', line 100 def parent_node if path.empty? raise(::JSON::Schema::Pointer::ReferenceError, "cannot access parent of root node: #{pretty_inspect.chomp}") else Node.new_by_type(document, path[0...-1]) end end |
#pointer_path ⇒ Object
the pointer path to this node within the document, per RFC 6901 tools.ietf.org/html/rfc6901
109 110 111 |
# File 'lib/scorpio/json/node.rb', line 109 def pointer_path pointer.pointer end |
#pretty_print(q) ⇒ Object
175 176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/scorpio/json/node.rb', line 175 def pretty_print(q) q.instance_exec(self) do |obj| text "\#<#{obj.class.inspect} #{obj.object_group_text}" group_sub { nest(2) { breakable ' ' pp obj.content } } breakable '' text '>' end end |