jsi 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/README.md +18 -15
- data/jsi.gemspec +1 -1
- data/lib/jsi.rb +5 -1
- data/lib/jsi/base.rb +222 -215
- data/lib/jsi/json-schema-fragments.rb +1 -1
- data/lib/jsi/json/node.rb +61 -146
- data/lib/jsi/json/pointer.rb +235 -41
- data/lib/jsi/pathed_node.rb +113 -0
- data/lib/jsi/schema.rb +46 -40
- data/lib/jsi/schema_classes.rb +86 -0
- data/lib/jsi/simple_wrap.rb +7 -0
- data/lib/jsi/typelike_modules.rb +39 -11
- data/lib/jsi/util.rb +3 -0
- data/lib/jsi/version.rb +1 -1
- data/test/base_array_test.rb +63 -51
- data/test/base_hash_test.rb +38 -28
- data/test/base_test.rb +54 -27
- data/test/jsi_json_arraynode_test.rb +19 -18
- data/test/jsi_json_hashnode_test.rb +29 -28
- data/test/jsi_json_node_test.rb +50 -28
- data/test/jsi_json_pointer_test.rb +13 -5
- data/test/schema_test.rb +13 -13
- data/test/spreedly_openapi_test.rb +8 -0
- data/test/test_helper.rb +3 -3
- data/test/util_test.rb +10 -10
- metadata +8 -3
@@ -0,0 +1,113 @@
|
|
1
|
+
module JSI
|
2
|
+
# including class MUST define
|
3
|
+
# - #node_document [Object] returning the document
|
4
|
+
# - #node_ptr [JSI::JSON::Pointer] returning a pointer for the node path in the document
|
5
|
+
# - #document_root_node [JSI::PathedNode] returning a PathedNode pointing at the document root
|
6
|
+
# - #parent_node [JSI::PathedNode] returning the parent node of this PathedNode
|
7
|
+
# - #deref [JSI::PathedNode] following a $ref
|
8
|
+
#
|
9
|
+
# given these, this module represents the node in the document at the path.
|
10
|
+
#
|
11
|
+
# the node content (#node_content) is the result of evaluating the node document at the path.
|
12
|
+
module PathedNode
|
13
|
+
def node_content
|
14
|
+
content = node_ptr.evaluate(node_document)
|
15
|
+
content
|
16
|
+
end
|
17
|
+
|
18
|
+
def node_ptr_deref(&block)
|
19
|
+
node_ptr.deref(node_document, &block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# module extending a {JSI::PathedNode} object when its node_content is Hash-like (responds to #to_hash)
|
24
|
+
module PathedHashNode
|
25
|
+
# yields each hash key and value of this node.
|
26
|
+
#
|
27
|
+
# each yielded key is the same as a key of the node content hash,
|
28
|
+
# and each yielded value is the result of self[key] (see #[]).
|
29
|
+
#
|
30
|
+
# returns an Enumerator if no block is given.
|
31
|
+
#
|
32
|
+
# @yield [Object, Object] each key and value of this hash node
|
33
|
+
# @return [self, Enumerator]
|
34
|
+
def each(&block)
|
35
|
+
return to_enum(__method__) { node_content_hash_pubsend(:size) } unless block_given?
|
36
|
+
if block.arity > 1
|
37
|
+
node_content_hash_pubsend(:each_key) { |k| yield k, self[k] }
|
38
|
+
else
|
39
|
+
node_content_hash_pubsend(:each_key) { |k| yield [k, self[k]] }
|
40
|
+
end
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [Hash] a hash in which each key is a key of the node_content hash and
|
45
|
+
# each value is the result of self[key] (see #[]).
|
46
|
+
def to_hash
|
47
|
+
{}.tap { |h| each_key { |k| h[k] = self[k] } }
|
48
|
+
end
|
49
|
+
|
50
|
+
include Hashlike
|
51
|
+
|
52
|
+
# @param method_name [String, Symbol]
|
53
|
+
# @param *a, &b are passed to the invocation of method_name
|
54
|
+
# @return [Object] the result of calling method method_name on the node_content or its #to_hash
|
55
|
+
def node_content_hash_pubsend(method_name, *a, &b)
|
56
|
+
if node_content.respond_to?(method_name)
|
57
|
+
node_content.public_send(method_name, *a, &b)
|
58
|
+
else
|
59
|
+
node_content.to_hash.public_send(method_name, *a, &b)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# methods that don't look at the value; can skip the overhead of #[] (invoked by #to_hash)
|
64
|
+
SAFE_KEY_ONLY_METHODS.each do |method_name|
|
65
|
+
define_method(method_name) do |*a, &b|
|
66
|
+
node_content_hash_pubsend(method_name, *a, &b)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
module PathedArrayNode
|
72
|
+
# yields each array element of this node.
|
73
|
+
#
|
74
|
+
# each yielded element is the result of self[index] for each index of our array (see #[]).
|
75
|
+
#
|
76
|
+
# returns an Enumerator if no block is given.
|
77
|
+
#
|
78
|
+
# @yield [Object] each element of this array node
|
79
|
+
# @return [self, Enumerator]
|
80
|
+
def each
|
81
|
+
return to_enum(__method__) { node_content_ary_pubsend(:size) } unless block_given?
|
82
|
+
node_content_ary_pubsend(:each_index) { |i| yield(self[i]) }
|
83
|
+
self
|
84
|
+
end
|
85
|
+
|
86
|
+
# @return [Array] an array, the same size as the node_content, in which the
|
87
|
+
# element at each index is the result of self[index] (see #[])
|
88
|
+
def to_ary
|
89
|
+
to_a
|
90
|
+
end
|
91
|
+
|
92
|
+
include Arraylike
|
93
|
+
|
94
|
+
# @param method_name [String, Symbol]
|
95
|
+
# @param *a, &b are passed to the invocation of method_name
|
96
|
+
# @return [Object] the result of calling method method_name on the node_content or its #to_ary
|
97
|
+
def node_content_ary_pubsend(method_name, *a, &b)
|
98
|
+
if node_content.respond_to?(method_name)
|
99
|
+
node_content.public_send(method_name, *a, &b)
|
100
|
+
else
|
101
|
+
node_content.to_ary.public_send(method_name, *a, &b)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# methods that don't look at the value; can skip the overhead of #[] (invoked by #to_a).
|
106
|
+
# we override these methods from Arraylike
|
107
|
+
SAFE_INDEX_ONLY_METHODS.each do |method_name|
|
108
|
+
define_method(method_name) do |*a, &b|
|
109
|
+
node_content_ary_pubsend(method_name, *a, &b)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
data/lib/jsi/schema.rb
CHANGED
@@ -8,6 +8,9 @@ module JSI
|
|
8
8
|
include Memoize
|
9
9
|
|
10
10
|
class << self
|
11
|
+
# @param schema_object [#to_hash, Boolean, JSI::Schema] an object to be instantiated as a schema.
|
12
|
+
# if it's already a schema, it is returned as-is.
|
13
|
+
# @return [JSI::Schema]
|
11
14
|
def from_object(schema_object)
|
12
15
|
if schema_object.is_a?(Schema)
|
13
16
|
schema_object
|
@@ -17,36 +20,30 @@ module JSI
|
|
17
20
|
end
|
18
21
|
end
|
19
22
|
|
20
|
-
# initializes a schema from a given JSI::Base, JSI::JSON::Node, or hash.
|
21
|
-
#
|
23
|
+
# initializes a schema from a given JSI::Base, JSI::JSON::Node, or hash. Boolean schemas are
|
24
|
+
# instantiated as their equivalent hash ({} for true and {"not" => {}} for false).
|
25
|
+
#
|
26
|
+
# @param schema_object [JSI::Base, #to_hash, Boolean] the schema
|
22
27
|
def initialize(schema_object)
|
23
28
|
if schema_object.is_a?(JSI::Schema)
|
24
29
|
raise(TypeError, "will not instantiate Schema from another Schema: #{schema_object.pretty_inspect.chomp}")
|
25
|
-
elsif schema_object.is_a?(JSI::
|
26
|
-
@schema_jsi = JSI.deep_stringify_symbol_keys(schema_object.deref)
|
27
|
-
@schema_node = @schema_jsi.instance
|
28
|
-
elsif schema_object.is_a?(JSI::JSON::HashNode)
|
29
|
-
@schema_jsi = nil
|
30
|
+
elsif schema_object.is_a?(JSI::PathedNode)
|
30
31
|
@schema_node = JSI.deep_stringify_symbol_keys(schema_object.deref)
|
31
32
|
elsif schema_object.respond_to?(:to_hash)
|
32
|
-
@schema_jsi = nil
|
33
33
|
@schema_node = JSI::JSON::Node.new_doc(JSI.deep_stringify_symbol_keys(schema_object))
|
34
|
+
elsif schema_object == true
|
35
|
+
@schema_node = JSI::JSON::Node.new_doc({})
|
36
|
+
elsif schema_object == false
|
37
|
+
@schema_node = JSI::JSON::Node.new_doc({"not" => {}})
|
34
38
|
else
|
35
39
|
raise(TypeError, "cannot instantiate Schema from: #{schema_object.pretty_inspect.chomp}")
|
36
40
|
end
|
37
41
|
end
|
38
42
|
|
39
|
-
# @return [JSI::
|
43
|
+
# @return [JSI::PathedNode] a JSI::PathedNode (JSI::JSON::Node or JSI::Base) for the schema
|
40
44
|
attr_reader :schema_node
|
41
45
|
|
42
|
-
|
43
|
-
attr_reader :schema_jsi
|
44
|
-
|
45
|
-
# @return [JSI::Base, JSI::JSON::Node] either a JSI::Base subclass or a
|
46
|
-
# JSI::JSON::Node for the schema
|
47
|
-
def schema_object
|
48
|
-
@schema_jsi || @schema_node
|
49
|
-
end
|
46
|
+
alias_method :schema_object, :schema_node
|
50
47
|
|
51
48
|
# @return [JSI::Base, JSI::JSON::Node, Object] property value from the schema_object
|
52
49
|
# @param property_name [String, Object] property name to access from the schema_object
|
@@ -69,7 +66,7 @@ module JSI
|
|
69
66
|
# look at 'id' if node_for_id is a schema, or the document root.
|
70
67
|
# decide whether to look at '$id' for all parent nodes or also just schemas.
|
71
68
|
if node_for_id.respond_to?(:to_hash)
|
72
|
-
if node_for_id.
|
69
|
+
if node_for_id.node_ptr.root? || node_for_id.object_id == schema_node.object_id
|
73
70
|
# I'm only looking at 'id' for the document root and the schema node
|
74
71
|
# until I track what parents are schemas.
|
75
72
|
parent_id = node_for_id['$id'] || node_for_id['id']
|
@@ -80,30 +77,30 @@ module JSI
|
|
80
77
|
end
|
81
78
|
end
|
82
79
|
|
83
|
-
if parent_id || node_for_id.
|
80
|
+
if parent_id || node_for_id.node_ptr.root?
|
84
81
|
done = true
|
85
82
|
else
|
86
|
-
path_from_id_node.unshift(node_for_id.
|
83
|
+
path_from_id_node.unshift(node_for_id.node_ptr.reference_tokens.last)
|
87
84
|
node_for_id = node_for_id.parent_node
|
88
85
|
end
|
89
86
|
end
|
90
87
|
if parent_id
|
91
88
|
parent_auri = Addressable::URI.parse(parent_id)
|
92
89
|
else
|
93
|
-
node_for_id = schema_node.
|
94
|
-
validator = ::JSON::Validator.new(node_for_id
|
90
|
+
node_for_id = schema_node.document_root_node
|
91
|
+
validator = ::JSON::Validator.new(Typelike.as_json(node_for_id), nil)
|
95
92
|
# TODO not good instance_exec'ing into another library's ivars
|
96
93
|
parent_auri = validator.instance_exec { @base_schema }.uri
|
97
94
|
end
|
98
95
|
if parent_auri.fragment
|
99
96
|
# add onto the fragment
|
100
|
-
parent_id_path = JSI::JSON::Pointer.
|
97
|
+
parent_id_path = JSI::JSON::Pointer.from_fragment('#' + parent_auri.fragment).reference_tokens
|
101
98
|
path_from_id_node = parent_id_path + path_from_id_node
|
102
99
|
parent_auri.fragment = nil
|
103
100
|
#else: no fragment so parent_id good as is
|
104
101
|
end
|
105
102
|
|
106
|
-
fragment = JSI::JSON::Pointer.new(
|
103
|
+
fragment = JSI::JSON::Pointer.new(path_from_id_node).fragment
|
107
104
|
schema_id = parent_auri.to_s + fragment
|
108
105
|
|
109
106
|
schema_id
|
@@ -111,26 +108,35 @@ module JSI
|
|
111
108
|
end
|
112
109
|
|
113
110
|
# @return [Class subclassing JSI::Base] shortcut for JSI.class_for_schema(schema)
|
114
|
-
def
|
111
|
+
def jsi_schema_class
|
115
112
|
JSI.class_for_schema(self)
|
116
113
|
end
|
117
114
|
|
115
|
+
alias_method :schema_class, :jsi_schema_class
|
116
|
+
|
117
|
+
# calls #new on the class for this schema with the given arguments. for parameters,
|
118
|
+
# see JSI::Base#initialize documentation.
|
119
|
+
#
|
120
|
+
# @return [JSI::Base] a JSI whose schema is this schema and whose instance is the given instance
|
121
|
+
def new_jsi(other_instance, *a, &b)
|
122
|
+
jsi_schema_class.new(other_instance, *a, &b)
|
123
|
+
end
|
124
|
+
|
118
125
|
# if this schema is a oneOf, allOf, anyOf schema, #match_to_instance finds
|
119
126
|
# one of the subschemas that matches the given instance and returns it. if
|
120
127
|
# there are no matching *Of schemas, this schema is returned.
|
121
128
|
#
|
122
|
-
# @param
|
129
|
+
# @param other_instance [Object] the instance to which to attempt to match *Of subschemas
|
123
130
|
# @return [JSI::Schema] a matched subschema, or this schema (self)
|
124
|
-
def match_to_instance(
|
131
|
+
def match_to_instance(other_instance)
|
125
132
|
# matching oneOf is good here. one schema for one instance.
|
126
133
|
# matching anyOf is okay. there could be more than one schema matched. it's often just one. if more
|
127
134
|
# than one is a match, you just get the first one.
|
128
|
-
instance = instance.deref if instance.is_a?(JSI::JSON::Node)
|
129
135
|
%w(oneOf anyOf).select { |k| schema_node[k].respond_to?(:to_ary) }.each do |someof_key|
|
130
136
|
schema_node[someof_key].map(&:deref).map do |someof_node|
|
131
137
|
someof_schema = self.class.new(someof_node)
|
132
|
-
if someof_schema.
|
133
|
-
return someof_schema.match_to_instance(
|
138
|
+
if someof_schema.validate_instance(other_instance)
|
139
|
+
return someof_schema.match_to_instance(other_instance)
|
134
140
|
end
|
135
141
|
end
|
136
142
|
end
|
@@ -227,33 +233,33 @@ module JSI
|
|
227
233
|
|
228
234
|
# @return [Array<String>] array of schema validation error messages for
|
229
235
|
# the given instance against this schema
|
230
|
-
def
|
231
|
-
::JSON::Validator.fully_validate(JSI::Typelike.as_json(schema_node.
|
236
|
+
def fully_validate_instance(other_instance)
|
237
|
+
::JSON::Validator.fully_validate(JSI::Typelike.as_json(schema_node.node_document), JSI::Typelike.as_json(other_instance), fragment: schema_node.node_ptr.fragment)
|
232
238
|
end
|
233
239
|
|
234
240
|
# @return [true, false] whether the given instance validates against this schema
|
235
|
-
def
|
236
|
-
::JSON::Validator.validate(JSI::Typelike.as_json(schema_node.
|
241
|
+
def validate_instance(other_instance)
|
242
|
+
::JSON::Validator.validate(JSI::Typelike.as_json(schema_node.node_document), JSI::Typelike.as_json(other_instance), fragment: schema_node.node_ptr.fragment)
|
237
243
|
end
|
238
244
|
|
239
245
|
# @return [true] if this method does not raise, it returns true to
|
240
246
|
# indicate the instance is valid against this schema
|
241
247
|
# @raise [::JSON::Schema::ValidationError] raises if the instance has
|
242
248
|
# validation errors against this schema
|
243
|
-
def
|
244
|
-
::JSON::Validator.validate!(JSI::Typelike.as_json(schema_node.
|
249
|
+
def validate_instance!(other_instance)
|
250
|
+
::JSON::Validator.validate!(JSI::Typelike.as_json(schema_node.node_document), JSI::Typelike.as_json(other_instance), fragment: schema_node.node_ptr.fragment)
|
245
251
|
end
|
246
252
|
|
247
253
|
# @return [Array<String>] array of schema validation error messages for
|
248
254
|
# this schema, validated against its metaschema. a default metaschema
|
249
255
|
# is assumed if the schema does not specify a $schema.
|
250
256
|
def fully_validate_schema
|
251
|
-
::JSON::Validator.fully_validate(JSI::Typelike.as_json(schema_node.
|
257
|
+
::JSON::Validator.fully_validate(JSI::Typelike.as_json(schema_node.node_document), [], fragment: schema_node.node_ptr.fragment, validate_schema: true, list: true)
|
252
258
|
end
|
253
259
|
|
254
260
|
# @return [true, false] whether this schema validates against its metaschema
|
255
261
|
def validate_schema
|
256
|
-
::JSON::Validator.validate(JSI::Typelike.as_json(schema_node.
|
262
|
+
::JSON::Validator.validate(JSI::Typelike.as_json(schema_node.node_document), [], fragment: schema_node.node_ptr.fragment, validate_schema: true, list: true)
|
257
263
|
end
|
258
264
|
|
259
265
|
# @return [true] if this method does not raise, it returns true to
|
@@ -261,7 +267,7 @@ module JSI
|
|
261
267
|
# @raise [::JSON::Schema::ValidationError] raises if this schema has
|
262
268
|
# validation errors against its metaschema
|
263
269
|
def validate_schema!
|
264
|
-
::JSON::Validator.validate!(JSI::Typelike.as_json(schema_node.
|
270
|
+
::JSON::Validator.validate!(JSI::Typelike.as_json(schema_node.node_document), [], fragment: schema_node.node_ptr.fragment, validate_schema: true, list: true)
|
265
271
|
end
|
266
272
|
|
267
273
|
# @return [String] a string for #instance and #pretty_print including the schema_id
|
@@ -298,7 +304,7 @@ module JSI
|
|
298
304
|
|
299
305
|
# @return [Object] an opaque fingerprint of this Schema for FingerprintHash
|
300
306
|
def fingerprint
|
301
|
-
{class: self.class, schema_node: schema_node}
|
307
|
+
{class: self.class, schema_ptr: schema_node.node_ptr, schema_document: JSI::Typelike.as_json(schema_node.node_document)}
|
302
308
|
end
|
303
309
|
include FingerprintHash
|
304
310
|
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module JSI
|
2
|
+
# this module is just a namespace for schema classes.
|
3
|
+
module SchemaClasses
|
4
|
+
# JSI::SchemaClasses[schema_id] returns a class for the schema with the
|
5
|
+
# given id, the same class as returned from JSI.class_for_schema.
|
6
|
+
#
|
7
|
+
# @param schema_id [String] absolute schema id as returned by {JSI::Schema#schema_id}
|
8
|
+
# @return [Class subclassing JSI::Base] the class for that schema
|
9
|
+
def self.[](schema_id)
|
10
|
+
@classes_by_id[schema_id]
|
11
|
+
end
|
12
|
+
@classes_by_id = {}
|
13
|
+
|
14
|
+
class << self
|
15
|
+
include Memoize
|
16
|
+
|
17
|
+
# see {JSI.class_for_schema}
|
18
|
+
def class_for_schema(schema_object)
|
19
|
+
memoize(:class_for_schema, JSI::Schema.from_object(schema_object)) do |schema_|
|
20
|
+
Class.new(Base).instance_exec(schema_) do |schema|
|
21
|
+
define_singleton_method(:schema) { schema }
|
22
|
+
define_method(:schema) { schema }
|
23
|
+
include(JSI::SchemaClasses.module_for_schema(schema, conflicting_modules: [Base, BaseArray, BaseHash]))
|
24
|
+
|
25
|
+
jsi_class = self
|
26
|
+
define_method(:jsi_class) { jsi_class }
|
27
|
+
|
28
|
+
SchemaClasses.instance_exec(self) { |klass| @classes_by_id[klass.schema_id] = klass }
|
29
|
+
|
30
|
+
self
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# a module for the given schema, with accessor methods for any object
|
36
|
+
# property names the schema identifies. also has a singleton method
|
37
|
+
# called #schema to access the {JSI::Schema} this module represents.
|
38
|
+
#
|
39
|
+
# accessor methods are defined on these modules so that methods can be
|
40
|
+
# defined on {JSI.class_for_schema} classes without method redefinition
|
41
|
+
# warnings. additionally, these overriding instance methods can call
|
42
|
+
# `super` to invoke the normal accessor behavior.
|
43
|
+
#
|
44
|
+
# no property names that are the same as existing method names on the JSI
|
45
|
+
# class will be defined. users should use #[] and #[]= to access properties
|
46
|
+
# whose names conflict with existing methods.
|
47
|
+
def SchemaClasses.module_for_schema(schema_object, conflicting_modules: [])
|
48
|
+
schema__ = JSI::Schema.from_object(schema_object)
|
49
|
+
memoize(:module_for_schema, schema__, conflicting_modules) do |schema_, conflicting_modules_|
|
50
|
+
Module.new.tap do |m|
|
51
|
+
m.instance_exec(schema_) do |schema|
|
52
|
+
define_singleton_method(:schema) { schema }
|
53
|
+
define_singleton_method(:schema_id) do
|
54
|
+
schema.schema_id
|
55
|
+
end
|
56
|
+
define_singleton_method(:inspect) do
|
57
|
+
%Q(#<Module for Schema: #{schema_id}>)
|
58
|
+
end
|
59
|
+
|
60
|
+
conflicting_instance_methods = (conflicting_modules_ + [m]).map do |mod|
|
61
|
+
mod.instance_methods + mod.private_instance_methods
|
62
|
+
end.inject(Set.new, &:|)
|
63
|
+
accessors_to_define = schema.described_object_property_names.map(&:to_s) - conflicting_instance_methods.map(&:to_s)
|
64
|
+
accessors_to_define.each do |property_name|
|
65
|
+
define_method(property_name) do
|
66
|
+
if respond_to?(:[])
|
67
|
+
self[property_name]
|
68
|
+
else
|
69
|
+
raise(NoMethodError, "schema instance of class #{self.class} does not respond to []; cannot call reader '#{property_name}'. instance is #{instance.pretty_inspect.chomp}")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
define_method("#{property_name}=") do |value|
|
73
|
+
if respond_to?(:[]=)
|
74
|
+
self[property_name] = value
|
75
|
+
else
|
76
|
+
raise(NoMethodError, "schema instance of class #{self.class} does not respond to []=; cannot call writer '#{property_name}='. instance is #{instance.pretty_inspect.chomp}")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/lib/jsi/typelike_modules.rb
CHANGED
@@ -36,12 +36,8 @@ module JSI
|
|
36
36
|
# @raise [TypeError] when the object (or an object nested with a hash or
|
37
37
|
# array of object) cannot be expressed as json
|
38
38
|
def self.as_json(object, *opt)
|
39
|
-
if object.is_a?(JSI::
|
40
|
-
as_json(object.
|
41
|
-
elsif object.is_a?(JSI::Base)
|
42
|
-
as_json(object.instance, *opt)
|
43
|
-
elsif object.is_a?(JSI::JSON::Node)
|
44
|
-
as_json(object.content, *opt)
|
39
|
+
if object.is_a?(JSI::PathedNode)
|
40
|
+
as_json(object.node_content, *opt)
|
45
41
|
elsif object.respond_to?(:to_hash)
|
46
42
|
(object.respond_to?(:map) ? object : object.to_hash).map do |k, v|
|
47
43
|
unless k.is_a?(Symbol) || k.respond_to?(:to_str)
|
@@ -78,7 +74,7 @@ module JSI
|
|
78
74
|
SAFE_KEY_VALUE_METHODS = %w(< <= > >= any? assoc compact dig each_pair each_value fetch fetch_values has_value? invert key merge rassoc reject select to_h to_proc transform_values value? values values_at)
|
79
75
|
DESTRUCTIVE_METHODS = %w(clear delete delete_if keep_if reject! replace select! shift)
|
80
76
|
# these return a modified copy
|
81
|
-
safe_modified_copy_methods = %w(compact
|
77
|
+
safe_modified_copy_methods = %w(compact)
|
82
78
|
# select and reject will return a modified copy but need the yielded block variable value from #[]
|
83
79
|
safe_kv_block_modified_copy_methods = %w(select reject)
|
84
80
|
SAFE_METHODS = SAFE_KEY_ONLY_METHODS | SAFE_KEY_VALUE_METHODS
|
@@ -88,7 +84,7 @@ module JSI
|
|
88
84
|
end
|
89
85
|
safe_modified_copy_methods.each do |method_name|
|
90
86
|
define_method(method_name) do |*a, &b|
|
91
|
-
|
87
|
+
modified_copy do |object_to_modify|
|
92
88
|
responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_hash
|
93
89
|
responsive_object.public_send(method_name, *a, &b)
|
94
90
|
end
|
@@ -96,7 +92,7 @@ module JSI
|
|
96
92
|
end
|
97
93
|
safe_kv_block_modified_copy_methods.each do |method_name|
|
98
94
|
define_method(method_name) do |*a, &b|
|
99
|
-
|
95
|
+
modified_copy do |object_to_modify|
|
100
96
|
responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_hash
|
101
97
|
responsive_object.public_send(method_name, *a) do |k, _v|
|
102
98
|
b.call(k, self[k])
|
@@ -105,6 +101,38 @@ module JSI
|
|
105
101
|
end
|
106
102
|
end
|
107
103
|
|
104
|
+
# the same as Hash#update
|
105
|
+
# @param other [#to_hash] the other hash to update this hash from
|
106
|
+
# @yield [key, oldval, newval] for entries with duplicate keys, the value of each duplicate key
|
107
|
+
# is determined by calling the block with the key, its value in hsh and its value in other_hash.
|
108
|
+
# @return self, updated with other
|
109
|
+
# @raise [TypeError] when `other` does not respond to #to_hash
|
110
|
+
def update(other, &block)
|
111
|
+
unless other.respond_to?(:to_hash)
|
112
|
+
raise(TypeError, "cannot update with argument that does not respond to #to_hash: #{other.pretty_inspect.chomp}")
|
113
|
+
end
|
114
|
+
self_respondingto_key = self.respond_to?(:key?) ? self : to_hash
|
115
|
+
other.to_hash.each_pair do |key, value|
|
116
|
+
if block_given? && self_respondingto_key.key?(key)
|
117
|
+
value = yield(key, self[key], value)
|
118
|
+
end
|
119
|
+
self[key] = value
|
120
|
+
end
|
121
|
+
self
|
122
|
+
end
|
123
|
+
|
124
|
+
alias_method :merge!, :update
|
125
|
+
|
126
|
+
# the same as Hash#merge
|
127
|
+
# @param other [#to_hash] the other hash to merge into this
|
128
|
+
# @yield [key, oldval, newval] for entries with duplicate keys, the value of each duplicate key
|
129
|
+
# is determined by calling the block with the key, its value in hsh and its value in other_hash.
|
130
|
+
# @return duplicate of this hash with the other hash merged in
|
131
|
+
# @raise [TypeError] when `other` does not respond to #to_hash
|
132
|
+
def merge(other, &block)
|
133
|
+
dup.update(other, &block)
|
134
|
+
end
|
135
|
+
|
108
136
|
# @return [String] basically the same #inspect as Hash, but has the
|
109
137
|
# class name and, if responsive, self's #object_group_text
|
110
138
|
def inspect
|
@@ -168,7 +196,7 @@ module JSI
|
|
168
196
|
end
|
169
197
|
safe_modified_copy_methods.each do |method_name|
|
170
198
|
define_method(method_name) do |*a, &b|
|
171
|
-
|
199
|
+
modified_copy do |object_to_modify|
|
172
200
|
responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_ary
|
173
201
|
responsive_object.public_send(method_name, *a, &b)
|
174
202
|
end
|
@@ -176,7 +204,7 @@ module JSI
|
|
176
204
|
end
|
177
205
|
safe_el_block_methods.each do |method_name|
|
178
206
|
define_method(method_name) do |*a, &b|
|
179
|
-
|
207
|
+
modified_copy do |object_to_modify|
|
180
208
|
i = 0
|
181
209
|
responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_ary
|
182
210
|
responsive_object.public_send(method_name, *a) do |_e|
|