Module: JSI::Util
Overview
JSI::Util contains public utilities
Defined Under Namespace
Modules: Arraylike, Hashlike, Private
Constant Summary
Constants included from Private
Private::CLASSES_ALWAYS_FROZEN, Private::EMPTY_ARY, Private::EMPTY_SET, Private::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS, Private::RUBY_REJECT_NAME_CODEPOINTS, Private::RUBY_REJECT_NAME_RE, Private::USE_TO_JSON_METHOD
Instance Method Summary collapse
-
#as_json(object, options = {}) ⇒ Array, ...
A structure like the given
object
, recursively coerced to JSON-compatible types. - #deep_stringify_symbol_keys(object) ⇒ Object
-
#deep_to_frozen(object, not_implemented: nil) ⇒ Object
returns an object which is equal to the param object, and is recursively frozen.
-
#ensure_module_set(modules) ⇒ Set
ensures the given param becomes a frozen Set of Modules.
-
#modified_copy(object) {|Object| ... } ⇒ object.class
yields the content of the given param
object
. -
#stringify_symbol_keys(hashlike) ⇒ same class as the param `hash`, or Hash if the former cannot be done
a hash copied from the given hashlike, in which any symbol keys are converted to strings.
-
#to_json(object, options = {}) ⇒ String
A JSON encoded string of the given object.
Methods included from Private
#const_name_from_parts, #ok_ruby_method_name?, #require_jmespath, #uri, #ycomb
Instance Method Details
#as_json(object, options = {}) ⇒ Array, ...
A structure like the given object
, recursively coerced to JSON-compatible types.
- Structures of Hash, Array, and basic types of String/number/boolean/nil are returned as-is.
- If the object responds to
#as_json
, that method is used, passing any given options. - If the object supports implicit conversion
with
#to_hash
,#to_ary
,#to_str
, or#to_int
, that is used. - Set becomes Array; Symbol becomes String.
- Types with no known coersion to JSON-compatible raise TypeError.
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/jsi/util.rb', line 45 def as_json(object, = {}) type_err = proc { raise(TypeError, "cannot express object as json: #{object.pretty_inspect.chomp}") } if object.respond_to?(:as_json) .empty? ? object.as_json : object.as_json(**) # TODO remove eventually (keyword argument compatibility) elsif object.is_a?(Addressable::URI) object.to_s elsif object.respond_to?(:to_hash) && (object_to_hash = object.to_hash).is_a?(Hash) result = {} object_to_hash.each_pair do |k, v| ks = k.is_a?(String) ? k : k.is_a?(Symbol) ? k.to_s : k.respond_to?(:to_str) && (kstr = k.to_str).is_a?(String) ? kstr : raise(TypeError, "json object (hash) cannot be keyed with: #{k.pretty_inspect.chomp}") result[ks] = as_json(v, **) end result elsif object.respond_to?(:to_ary) && (object_to_ary = object.to_ary).is_a?(Array) object_to_ary.map { |e| as_json(e, **) } elsif [String, Integer, TrueClass, FalseClass, NilClass].any? { |c| object.is_a?(c) } object elsif object.is_a?(Float) type_err.call unless object.finite? object elsif object.is_a?(Symbol) object.to_s elsif object.is_a?(Set) as_json(object.to_a, **) elsif object.respond_to?(:to_str) && (object_to_str = object.to_str).is_a?(String) object_to_str elsif object.respond_to?(:to_int) && (object_to_int = object.to_int).is_a?(Integer) object_to_int else type_err.call end end |
#deep_stringify_symbol_keys(object) ⇒ Object
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/jsi/util.rb', line 122 def deep_stringify_symbol_keys(object) if object.respond_to?(:to_hash) && !object.is_a?(Addressable::URI) JSI::Util.modified_copy(object) do |hash| out = {} (hash.respond_to?(:each) ? hash : hash.to_hash).each do |k, v| out[k.is_a?(Symbol) ? k.to_s.freeze : deep_stringify_symbol_keys(k)] = deep_stringify_symbol_keys(v) end out end elsif object.respond_to?(:to_ary) JSI::Util.modified_copy(object) do |ary| (ary.respond_to?(:each) ? ary : ary.to_ary).map do |e| deep_stringify_symbol_keys(e) end end else object end end |
#deep_to_frozen(object, not_implemented: nil) ⇒ Object
returns an object which is equal to the param object, and is recursively frozen. the given object is not modified.
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
# File 'lib/jsi/util.rb', line 144 def deep_to_frozen(object, not_implemented: nil) dtf = proc { |o| deep_to_frozen(o, not_implemented: not_implemented) } if object.instance_of?(Hash) out = {} identical = object.frozen? object.each do |k, v| fk = dtf[k] fv = dtf[v] identical &&= fk.__id__ == k.__id__ identical &&= fv.__id__ == v.__id__ out[fk] = fv end if !object.default.nil? out.default = dtf[object.default] identical &&= out.default.__id__ == object.default.__id__ end if object.default_proc raise(ArgumentError, "cannot make immutable copy of a Hash with default_proc") end if identical object else out.freeze end elsif object.instance_of?(Array) identical = object.frozen? out = Array.new(object.size) object.each_with_index do |e, i| fe = dtf[e] identical &&= fe.__id__ == e.__id__ out[i] = fe end if identical object else out.freeze end elsif object.instance_of?(String) if object.frozen? object else object.dup.freeze end elsif CLASSES_ALWAYS_FROZEN.any? { |c| object.is_a?(c) } # note: `is_a?`, not `instance_of?`, here because instance_of?(Integer) is false until Fixnum/Bignum is gone. this is fine here; there is no concern of subclasses of CLASSES_ALWAYS_FROZEN duping/freezing differently (as with e.g. ActiveSupport::HashWithIndifferentAccess) object else if not_implemented not_implemented.call(object) else raise(NotImplementedError, [ "deep_to_frozen not implemented for class: #{object.class}", "object: #{object.pretty_inspect.chomp}", ].join("\n")) end end end |
#ensure_module_set(modules) ⇒ Set
ensures the given param becomes a frozen Set of Modules. returns the param if it is already that, otherwise initializes and freezes such a Set.
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'lib/jsi/util.rb', line 208 def ensure_module_set(modules) if modules.is_a?(Set) && modules.frozen? set = modules elsif modules.is_a?(Enumerable) set = Set.new(modules).freeze else raise(TypeError, "not given an Enumerable of Modules") end not_modules = set.reject { |s| s.is_a?(Module) } if !not_modules.empty? raise(TypeError, [ "ensure_module_set given non-Module objects:", *not_modules.map { |ns| ns.pretty_inspect.chomp }, ].join("\n")) end set end |
#modified_copy(object) {|Object| ... } ⇒ object.class
yields the content of the given param object
. for objects which have a #jsi_modified_copy
method of their own (JSI::Base, JSI::MetaschemaNode) that method is invoked with the given
block. otherwise the given object itself is yielded.
the given block must result in a modified copy of its block parameter (not destructively modifying the yielded content).
25 26 27 28 29 30 31 |
# File 'lib/jsi/util.rb', line 25 def modified_copy(object, &block) if object.respond_to?(:jsi_modified_copy) object.jsi_modified_copy(&block) else yield(object) end end |
#stringify_symbol_keys(hashlike) ⇒ same class as the param `hash`, or Hash if the former cannot be done
a hash copied from the given hashlike, in which any symbol keys are converted to strings. behavior on collisions is undefined (but in the future could take a block like ActiveSupport::HashWithIndifferentAccess#update)
at the moment it is undefined whether the returned hash is the same
instance as the hash
param. if hash
is already a hash which contains
no symbol keys, this method MAY return that same instance. use #dup on
the return if you need to ensure it is not the same instance as the
argument instance.
109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/jsi/util.rb', line 109 def stringify_symbol_keys(hashlike) unless hashlike.respond_to?(:to_hash) raise(ArgumentError, "expected argument to be a hash; got #{hashlike.class.inspect}: #{hashlike.pretty_inspect.chomp}") end JSI::Util.modified_copy(hashlike) do |hash| out = {} hash.each do |k, v| out[k.is_a?(Symbol) ? k.to_s : k] = v end out end end |
#to_json(object, options = {}) ⇒ String
A JSON encoded string of the given object.
- If the object has a
#to_json
method that isn't defined by the stdlibjson
gem, that method is used, passing any given options. - Otherwise, JSON is generated using #as_json to coerce to compatible types.
87 88 89 90 91 92 93 |
# File 'lib/jsi/util.rb', line 87 def to_json(object, = {}) if USE_TO_JSON_METHOD[object.class] .empty? ? object.to_json : object.to_json(**) # TODO remove eventually (keyword argument compatibility) else JSON.generate(as_json(object, **)) end end |