jsi 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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|
|