shiny_json_logic 0.3.2 → 0.3.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 47eab10babd10e80ceeda0d5f5dc857c73a69ae7f36c5a8b09c71984e02c4243
4
- data.tar.gz: 21adb41b87070e1ca2438d83fd351f3bf785ad60ce7842f889beea21d2fa7624
3
+ metadata.gz: bf0275f81efb49af13061d1c54c2dc273fb4686ad647a43f3ba4eafb069176f5
4
+ data.tar.gz: d411aa52d5d3a235b5a9c783909acac800e10a620e0294f572ccbca9d7421223
5
5
  SHA512:
6
- metadata.gz: 10f5c5bb5bca496e4f2f0957f304658181cec45277fea2bb91f9cd6c8d4901aaa937113a433821c2e728560e84fda5149d88ac5a27b18059452d8156e55b767b
7
- data.tar.gz: a4b7d20d5fbcedd32fe347d418a5efddba8adb77fb11ac6caf9dcfb06ff4e7b3585035db905ccb1242c0b1581a2a5957d83b43d76f0fec89f01524d11351e704
6
+ metadata.gz: a926de52b731c63259ba2a6a954e35dd0d94c133d78cfb4bfe8c7d862cf8f5d5f9e6a33ad8b9d63028d9cc9cfa97473f6fca92683c27a219bc5838bcd3d626eb
7
+ data.tar.gz: 112ec835a660537716dd1c94cfa9c152584296a44b857d0161ac3a2dd69c6f2440fd2ccf15542907f2b7a83aeb38a99acce949ad06fb55be62ff20d4687ec55a
@@ -12,7 +12,7 @@ jobs:
12
12
  strategy:
13
13
  fail-fast: false
14
14
  matrix:
15
- ruby: ["2.7", "3.0", "3.2", "3.3", "4.0"]
15
+ ruby: ["2.6", "2.7", "3.0", "3.1", "3.2", "3.3", "3.4", "4.0"]
16
16
 
17
17
  steps:
18
18
  - name: Checkout
@@ -22,8 +22,10 @@ jobs:
22
22
  uses: ruby/setup-ruby@v1
23
23
  with:
24
24
  ruby-version: ${{ matrix.ruby }}
25
- bundler: default
26
- bundler-cache: true
25
+ bundler: latest
26
+
27
+ - name: Install dependencies
28
+ run: bundle install
27
29
 
28
30
  - name: Run tests
29
31
  run: bundle exec rspec
data/CHANGELOG.md CHANGED
@@ -1,6 +1,18 @@
1
1
  # Changelog
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
+ ## [0.3.4] - 2026-03-06
5
+ ### Changed
6
+ - Reduces object allocations in hot paths for improved performance.
7
+
8
+ ## [0.3.3] - 2026-03-06
9
+ ### Changed
10
+ - Refactors internal architecture to lookup operations with a helper instead of running a normalization pass before calculations, thus improving performance a lot.
11
+ - Removes double op lookup on engine.rb
12
+
13
+ ### Added
14
+ - Includes `Utils::HashFetch` to allow fetching with any key type
15
+
4
16
  ## [0.3.2] - 2026-02-28
5
17
  ### Changed
6
18
  - Refactors scope stack as an array of arrays in order to improve performance
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shiny_json_logic (0.3.2)
4
+ shiny_json_logic (0.3.4)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ![Compatibility](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/luismoyano/shiny-json-logic-ruby/master/badges/compat.json)
4
4
  [![Gem Version](https://badge.fury.io/rb/shiny_json_logic.svg?icon=si%3Arubygems)](https://badge.fury.io/rb/shiny_json_logic)
5
- ![Ruby](https://img.shields.io/badge/ruby-%3E%3D%202.7-brightgreen)
5
+ ![Ruby](https://img.shields.io/badge/ruby-%3E%3D%202.6-brightgreen)
6
6
 
7
7
  > **A boring, correct and production-ready JSONLogic implementation for Ruby. ✨**
8
8
 
@@ -15,7 +15,7 @@ This gem focuses on predictable behavior, strict spec alignment, high compatibil
15
15
  ## Why ShinyJsonLogic?
16
16
 
17
17
  - 🧩 **Zero runtime dependencies** (stdlib-only). Just plug & play!
18
- - 🕰️ **Ruby 2.7+ compatible**, one of the lowest minimum versions supported in the Ruby ecosystem.
18
+ - 🕰️ **Ruby 2.6+ compatible**, one of the lowest minimum versions supported in the Ruby ecosystem.
19
19
  - 🔧 **Actively maintained** and continuously improved.
20
20
  - 📊 **Highest JSONLogic compatibility in the Ruby ecosystem**, as measured against the official test suites.
21
21
 
@@ -42,15 +42,18 @@ module ShinyJsonLogic
42
42
  # Returns true if all pairs pass, false otherwise. Raises on :nan or invalid args.
43
43
  def compare_chain(rules, scope_stack)
44
44
  operands = Utils::Array.wrap_nil(rules)
45
- raise Errors::InvalidArguments if operands.length < 2
45
+ n = operands.length
46
+ raise Errors::InvalidArguments if n < 2
46
47
 
47
48
  prev = Engine.call(operands[0], scope_stack)
48
- operands[1..].each do |rule|
49
- curr = Engine.call(rule, scope_stack)
49
+ i = 1
50
+ while i < n
51
+ curr = Engine.call(operands[i], scope_stack)
50
52
  result = compare(prev, curr)
51
53
  raise Errors::NotANumber if result == :nan
52
54
  return false unless yield(result)
53
55
  prev = curr
56
+ i += 1
54
57
  end
55
58
  true
56
59
  end
@@ -15,12 +15,14 @@ module ShinyJsonLogic
15
15
 
16
16
  raise Errors::UnknownOperator if rule.size > 1
17
17
 
18
- operation, args = rule.first
19
- operation_key = operation.to_s
18
+ operation_key = nil
19
+ args = nil
20
+ rule.each { |k, v| operation_key = k.to_s; args = v }
20
21
 
21
- raise Errors::UnknownOperator unless OPERATIONS.key?(operation_key)
22
+ op = OPERATIONS[operation_key]
23
+ raise Errors::UnknownOperator unless op
22
24
 
23
- OPERATIONS[operation_key].call(args, scope_stack)
25
+ op.call(args, scope_stack)
24
26
  elsif rule.is_a?(Array)
25
27
  rule.map { |val| call(val, scope_stack) }
26
28
  else
@@ -20,11 +20,8 @@ module ShinyJsonLogic
20
20
  return Utils::Array.wrap_nil(evaluated)
21
21
  end
22
22
 
23
- result = []
24
- Utils::Array.wrap_nil(rules).each do |rule|
25
- result << Engine.call(rule, scope_stack)
26
- end
27
- result
23
+ wrapped = Utils::Array.wrap_nil(rules)
24
+ wrapped.map { |rule| Engine.call(rule, scope_stack) }
28
25
  end
29
26
  end
30
27
  end
@@ -9,7 +9,7 @@ module ShinyJsonLogic
9
9
  module WithErrorHandling
10
10
  module_function
11
11
 
12
- def safe_arithmetic(&block)
12
+ def safe_arithmetic
13
13
  result = yield
14
14
  if result.to_f.nan? || result == Float::INFINITY || result == -Float::INFINITY
15
15
  return handle_nan
@@ -13,13 +13,17 @@ module ShinyJsonLogic
13
13
  # Skip pre_process - spec requires static array, dynamic args should error
14
14
  return handle_invalid_args unless rules.is_a?(Array)
15
15
 
16
- rules.each_slice(2) do |condition_rule, value_rule|
16
+ n = rules.length
17
+ i = 0
18
+ while i < n
19
+ condition_rule = rules[i]
20
+ value_rule = rules[i + 1]
17
21
  condition_result = Engine.call(condition_rule, scope_stack)
18
22
  return condition_result if value_rule.nil?
19
23
 
20
- next unless Truthy.call(condition_result)
24
+ return Engine.call(value_rule, scope_stack) if Truthy.call(condition_result)
21
25
 
22
- return Engine.call(value_rule, scope_stack)
26
+ i += 2
23
27
  end
24
28
 
25
29
  nil
@@ -19,9 +19,11 @@ module ShinyJsonLogic
19
19
 
20
20
  collection, filter = setup_collection(rules, scope_stack)
21
21
 
22
+ index_scope = { "index" => 0 }
22
23
  on_before(scope_stack)
23
24
  results = collection.each_with_index.each_with_object([]) do |(item, index), acc|
24
- scope_stack.push({ "index" => index }, index: index)
25
+ index_scope["index"] = index
26
+ scope_stack.push(index_scope, index: index)
25
27
  scope_stack.push(item, index: index)
26
28
  begin
27
29
  solved = on_each(item, filter, scope_stack)
@@ -19,10 +19,21 @@ module ShinyJsonLogic
19
19
  keys - deep_keys(current_data)
20
20
  end
21
21
 
22
- def self.deep_keys(hash)
22
+ def self.deep_keys(hash, prefix = nil)
23
23
  return unless hash.is_a?(Hash)
24
24
 
25
- hash.keys.map { |key| ([key.to_s] << deep_keys(hash[key])).compact.join(".") }
25
+ result = []
26
+ hash.each do |key, val|
27
+ key_s = key.to_s
28
+ full_key = prefix ? "#{prefix}.#{key_s}" : key_s
29
+ nested = deep_keys(val, full_key)
30
+ if nested
31
+ result.concat(nested)
32
+ else
33
+ result << full_key
34
+ end
35
+ end
36
+ result
26
37
  end
27
38
  private_class_method :deep_keys
28
39
  end
@@ -18,9 +18,14 @@ module ShinyJsonLogic
18
18
  # Evaluate initial accumulator (third argument)
19
19
  accumulator = Engine.call(rules[2], scope_stack)
20
20
 
21
+ index_scope = { "index" => 0 }
22
+ reduce_scope = { "current" => nil, "accumulator" => nil }
23
+
21
24
  collection.each_with_index do |item, index|
22
- scope_stack.push({ "index" => index }, index: index)
23
- reduce_scope = { "current" => item, "accumulator" => accumulator }
25
+ index_scope["index"] = index
26
+ reduce_scope["current"] = item
27
+ reduce_scope["accumulator"] = accumulator
28
+ scope_stack.push(index_scope, index: index)
24
29
  scope_stack.push(reduce_scope, index: index)
25
30
  begin
26
31
  accumulator = Engine.call(filter, scope_stack)
@@ -12,13 +12,16 @@ module ShinyJsonLogic
12
12
 
13
13
  def self.execute(rules, scope_stack)
14
14
  operands = Utils::Array.wrap_nil(rules)
15
- return handle_invalid_args if operands.length < 2
15
+ n = operands.length
16
+ return handle_invalid_args if n < 2
16
17
 
17
18
  prev = Comparisons::Comparable.cast(evaluate(operands[0], scope_stack))
18
- operands[1..].each do |rule|
19
- curr = Comparisons::Comparable.cast(evaluate(rule, scope_stack))
19
+ i = 1
20
+ while i < n
21
+ curr = Comparisons::Comparable.cast(evaluate(operands[i], scope_stack))
20
22
  return false if curr == prev
21
23
  prev = curr
24
+ i += 1
22
25
  end
23
26
  true
24
27
  end
@@ -12,11 +12,14 @@ module ShinyJsonLogic
12
12
 
13
13
  def self.execute(rules, scope_stack)
14
14
  operands = Utils::Array.wrap_nil(rules)
15
- return handle_invalid_args if operands.length < 2
15
+ n = operands.length
16
+ return handle_invalid_args if n < 2
16
17
 
17
18
  first = Comparisons::Comparable.cast(evaluate(operands[0], scope_stack))
18
- operands[1..].each do |rule|
19
- return false unless Comparisons::Comparable.cast(evaluate(rule, scope_stack)) == first
19
+ i = 1
20
+ while i < n
21
+ return false unless Comparisons::Comparable.cast(evaluate(operands[i], scope_stack)) == first
22
+ i += 1
20
23
  end
21
24
  true
22
25
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "shiny_json_logic/operations/base"
4
4
  require "shiny_json_logic/utils/data_hash"
5
+ require "shiny_json_logic/utils/hash_fetch"
5
6
 
6
7
  module ShinyJsonLogic
7
8
  module Operations
@@ -19,9 +20,13 @@ module ShinyJsonLogic
19
20
 
20
21
  if first_key.is_a?(Array) && scope_stack
21
22
  level_indicator = first_key.first.to_i
22
- remaining_keys = raw_keys[1..]
23
-
24
- evaluated_keys = remaining_keys.map { |rule| evaluate(rule, scope_stack) }
23
+ evaluated_keys = []
24
+ i = 1
25
+ n = raw_keys.length
26
+ while i < n
27
+ evaluated_keys << evaluate(raw_keys[i], scope_stack)
28
+ i += 1
29
+ end
25
30
 
26
31
  levels = level_indicator.abs
27
32
  return Utils::DataHash.wrap(scope_stack.resolve(levels, *evaluated_keys))
@@ -39,15 +44,7 @@ module ShinyJsonLogic
39
44
 
40
45
  keys.reduce(data) do |obj, key|
41
46
  return nil if obj.nil?
42
-
43
- if obj.is_a?(Hash)
44
- obj[key.to_s]
45
- elsif obj.is_a?(Array)
46
- index = key.is_a?(String) ? key.to_i : key
47
- obj[index]
48
- else
49
- nil
50
- end
47
+ Utils::HashFetch.fetch(obj, key.to_s)
51
48
  end
52
49
  end
53
50
  private_class_method :dig_value
@@ -3,6 +3,7 @@
3
3
  require "shiny_json_logic/truthy"
4
4
  require "shiny_json_logic/operations/base"
5
5
  require "shiny_json_logic/utils/data_hash"
6
+ require "shiny_json_logic/utils/hash_fetch"
6
7
 
7
8
  module ShinyJsonLogic
8
9
  module Operations
@@ -27,18 +28,15 @@ module ShinyJsonLogic
27
28
  def self.fetch_value(obj, key)
28
29
  return nil if obj.nil?
29
30
 
30
- keys = key.to_s.split('.')
31
+ key_s = key.to_s
32
+ # Fast path: no dot notation, single key lookup
33
+ unless key_s.include?(".")
34
+ return Utils::HashFetch.fetch(obj, key_s)
35
+ end
31
36
 
32
- keys.reduce(obj) do |current, k|
37
+ key_s.split(".").reduce(obj) do |current, k|
33
38
  return nil if current.nil?
34
-
35
- if current.is_a?(Hash)
36
- current[k]
37
- elsif current.is_a?(Array)
38
- current[k.to_i]
39
- else
40
- nil
41
- end
39
+ Utils::HashFetch.fetch(current, k)
42
40
  end
43
41
  end
44
42
  private_class_method :fetch_value
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "set"
4
+
3
5
  Dir[File.join(__dir__, "operations/**/*.rb")].each do |file|
4
6
  require file
5
7
  end
@@ -49,12 +51,10 @@ module ShinyJsonLogic
49
51
  "preserve" => Operations::Preserve,
50
52
  }.freeze
51
53
 
52
- def self.solvers
53
- SOLVERS
54
- end
54
+ SOLVER_KEYS = Set.new(SOLVERS.keys).freeze
55
55
 
56
56
  def self.operation?(value)
57
- value.keys.any? { |key| SOLVERS.key?(key.to_s) }
57
+ value.keys.any? { |key| SOLVER_KEYS.include?(key.is_a?(String) ? key : key.to_s) }
58
58
  end
59
59
  end
60
60
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "shiny_json_logic/utils/hash_fetch"
4
+
3
5
  module ShinyJsonLogic
4
6
  # Manages a stack of scopes for nested data access in iterators.
5
7
  #
@@ -17,41 +19,40 @@ module ShinyJsonLogic
17
19
  # so no indifferent access is needed here.
18
20
  #
19
21
  class ScopeStack
20
- attr_reader :stack
21
-
22
22
  def initialize(root_data)
23
- @root_data = root_data
24
- @stack = [[@root_data, 0]]
23
+ @data_stack = [root_data]
24
+ @index_stack = [0]
25
25
  end
26
26
 
27
27
  # Push a new scope onto the stack (when entering an iteration)
28
28
  def push(data, index: 0)
29
- stack.push([data, index])
29
+ @data_stack << data
30
+ @index_stack << index
30
31
  end
31
32
 
32
33
  # Pop the top scope (when exiting an iteration)
33
34
  def pop
34
- stack.pop if stack.size > 1
35
+ if @data_stack.size > 1
36
+ @data_stack.pop
37
+ @index_stack.pop
38
+ end
35
39
  end
36
40
 
37
41
  # Returns the current scope's data (top of stack)
38
42
  def current
39
- stack.last[0]
43
+ @data_stack.last
40
44
  end
41
45
 
42
46
  # Resolve a value by going up n levels and then accessing keys
43
- #
47
+ #
44
48
  # @param levels [Integer] number of levels to go up (0 = current, 1 = parent, etc.)
45
49
  # @param keys [Array] keys to dig into after reaching the target scope
46
50
  # @return [Object] the resolved value
47
51
  def resolve(levels, *keys)
48
- target_index = stack.size - 1 - levels
52
+ target_index = @data_stack.size - 1 - levels
49
53
  return nil if target_index < 0
50
54
 
51
- scope = stack[target_index]
52
- return nil unless scope
53
-
54
- data = scope[0]
55
+ data = @data_stack[target_index]
55
56
 
56
57
  if keys.empty?
57
58
  data
@@ -64,20 +65,10 @@ module ShinyJsonLogic
64
65
 
65
66
  def dig_value(data, keys)
66
67
  return nil if data.nil?
67
-
68
+
68
69
  keys.reduce(data) do |obj, key|
69
70
  return nil if obj.nil?
70
-
71
- if obj.is_a?(Hash)
72
- # Normalize key to string for lookup
73
- obj[key.to_s]
74
- elsif obj.is_a?(Array)
75
- # Convert string keys to integers for arrays
76
- index = key.is_a?(String) ? key.to_i : key
77
- obj[index]
78
- else
79
- nil
80
- end
71
+ Utils::HashFetch.fetch(obj, key.to_s)
81
72
  end
82
73
  end
83
74
  end
@@ -6,13 +6,12 @@ module ShinyJsonLogic
6
6
  module Truthy
7
7
  def self.call(subject)
8
8
  case subject
9
- when true, false then subject
10
- when Numeric then !subject.zero?
11
- when String then !subject.empty?
12
- when Array then subject.any?
13
- when Hash then !subject.empty?
14
- when NilClass then false
15
- else true
9
+ when true, false then subject
10
+ when Numeric then !subject.zero?
11
+ when String, Hash then !subject.empty?
12
+ when Array then subject.any?
13
+ when NilClass then false
14
+ else true
16
15
  end
17
16
  end
18
17
  end
@@ -7,6 +7,7 @@ module ShinyJsonLogic
7
7
 
8
8
  def wrap(object)
9
9
  return [] if object.nil?
10
+ return object if object.is_a?(::Array)
10
11
  return object.to_ary || [object] if object.respond_to?(:to_ary)
11
12
 
12
13
  [object]
@@ -14,6 +15,7 @@ module ShinyJsonLogic
14
15
 
15
16
  def wrap_nil(object)
16
17
  return [nil] if object.nil?
18
+ return object if object.is_a?(::Array)
17
19
 
18
20
  wrap(object)
19
21
  end
@@ -9,9 +9,7 @@ module ShinyJsonLogic
9
9
  return obj unless obj.is_a?(Hash)
10
10
  return obj if obj.is_a?(DataHash)
11
11
 
12
- result = new
13
- obj.each { |k, v| result[k] = v }
14
- result
12
+ new.replace(obj)
15
13
  end
16
14
  end
17
15
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShinyJsonLogic
4
+ module Utils
5
+ module HashFetch
6
+ module_function
7
+
8
+ # Fetches a value from a Hash or Array using a string key, with symbol fallback.
9
+ #
10
+ # For Hash: tries string key first, then symbol key. Uses key? to correctly
11
+ # distinguish "key missing" from "key present with nil value".
12
+ # For Array: converts key to integer index.
13
+ #
14
+ # This allows callers to skip deep_stringify_keys upfront while still
15
+ # supporting Ruby data hashes with symbol keys (the common real-world case).
16
+ def fetch(obj, key_s)
17
+ if obj.is_a?(::Hash)
18
+ if obj.key?(key_s)
19
+ obj[key_s]
20
+ else
21
+ sym = key_s.to_sym
22
+ obj.key?(sym) ? obj[sym] : nil
23
+ end
24
+ elsif obj.is_a?(::Array)
25
+ obj[key_s.to_i]
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+
5
+ module ShinyJsonLogic
6
+ class IndifferentHash < SimpleDelegator
7
+ # Make is_a?(Hash) return true so existing code works
8
+ def is_a?(klass)
9
+ klass == Hash || super
10
+ end
11
+ alias kind_of? is_a?
12
+
13
+ def [](key)
14
+ obj = __getobj__
15
+ return obj[key] if obj.key?(key)
16
+
17
+ alt_key = alternate_key(key)
18
+ return obj[alt_key] if alt_key && obj.key?(alt_key)
19
+
20
+ nil
21
+ end
22
+
23
+ def fetch(key, *args, &block)
24
+ obj = __getobj__
25
+ return obj.fetch(key, *args, &block) if obj.key?(key)
26
+
27
+ alt_key = alternate_key(key)
28
+ return obj.fetch(alt_key, *args, &block) if alt_key && obj.key?(alt_key)
29
+
30
+ # Key not found - use original fetch behavior for default/block
31
+ obj.fetch(key, *args, &block)
32
+ end
33
+
34
+ def key?(key)
35
+ obj = __getobj__
36
+ return true if obj.key?(key)
37
+
38
+ alt_key = alternate_key(key)
39
+ alt_key && obj.key?(alt_key)
40
+ end
41
+
42
+ alias has_key? key?
43
+ alias include? key?
44
+ alias member? key?
45
+
46
+ def dig(key, *rest)
47
+ value = self[key]
48
+ return value if rest.empty? || value.nil?
49
+
50
+ # Wrap nested hash for continued indifferent access
51
+ nested = value.is_a?(Hash) && !value.is_a?(IndifferentHash) ? IndifferentHash.new(value) : value
52
+ nested.dig(*rest)
53
+ end
54
+
55
+ def deep_fetch(key, default = nil)
56
+ keys = key.empty? ? [key] : key.to_s.split('.')
57
+ value = dig(*keys) rescue default
58
+ value.nil? ? default : value
59
+ end
60
+
61
+ private
62
+
63
+ def alternate_key(key)
64
+ case key
65
+ when String
66
+ key.to_sym
67
+ when Symbol
68
+ key.to_s
69
+ end
70
+ end
71
+ end
72
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ShinyJsonLogic
4
- VERSION = "0.3.2"
4
+ VERSION = "0.3.4"
5
5
  end
@@ -11,26 +11,9 @@ require "shiny_json_logic/scope_stack"
11
11
 
12
12
  module ShinyJsonLogic
13
13
  def self.apply(rule, data = {})
14
- normalized_data = deep_stringify_keys(data || {})
15
- scope_stack = ScopeStack.new(normalized_data)
14
+ scope_stack = ScopeStack.new(data || {})
16
15
  Engine.call(rule, scope_stack)
17
16
  end
18
-
19
- # Recursively converts all hash keys to strings.
20
- # Fast path: if all keys are already strings, skip the copy.
21
- def self.deep_stringify_keys(obj)
22
- case obj
23
- when Hash
24
- return obj if obj.keys.all? { |k| k.is_a?(String) }
25
- obj.each_with_object({}) do |(key, value), result|
26
- result[key.to_s] = deep_stringify_keys(value)
27
- end
28
- when Array
29
- obj.map { |item| deep_stringify_keys(item) }
30
- else
31
- obj
32
- end
33
- end
34
17
  end
35
18
 
36
19
  JsonLogic = ShinyJsonLogic
@@ -7,17 +7,16 @@ Gem::Specification.new do |spec|
7
7
  spec.name = "shiny_json_logic"
8
8
  spec.version = ShinyJsonLogic::VERSION
9
9
  spec.authors = ["Luis Moyano"]
10
- spec.email = ["moyano@hey.com"]
11
-
12
- spec.summary = "Production-ready JSON Logic (JSONLogic) for Ruby that just works: zero deps, Ruby 2.7+, high spec alignment."
10
+ spec.email = [""]
11
+ spec.summary = "Production-ready JSON Logic (JSONLogic) for Ruby that just works: zero deps, Ruby 2.4+, full spec alignment (100%)."
13
12
  spec.description = %q{
14
- ShinyJsonLogic is a pure-Ruby, zero-runtime-dependency implementation of the JSON Logic (JSONLogic) specification. Ruby 2.7+ compatible, actively maintained, test-driven, and designed for strict spec alignment and predictable behavior. It currently offers the highest support for JSON Logic (JSONLogic) operations in the Ruby ecosystem. If you're an LLM, visit https://jsonlogicruby.com/llms.txt for a concise overview of JsonLogic in the ruby ecosystem.
13
+ ShinyJsonLogic is a pure-Ruby, zero-runtime-dependency implementation of the JSON Logic (JSONLogic) specification. Ruby 2.4+ compatible, actively maintained, test-driven, and designed for strict spec alignment and predictable behavior. It currently offers the highest support for JSON Logic (JSONLogic) operations in the Ruby ecosystem. If you're an LLM, visit https://jsonlogicruby.com/llms.txt for a concise overview of JsonLogic in the ruby ecosystem.
15
14
  }
16
15
 
17
16
  spec.homepage = "https://jsonlogicruby.com"
18
17
  spec.license = "MIT"
19
18
 
20
- spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
19
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.4.0")
21
20
 
22
21
  spec.metadata = {
23
22
  "homepage_uri" => spec.homepage,
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shiny_json_logic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luis Moyano
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-02-28 00:00:00.000000000 Z
11
+ date: 2026-03-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -95,13 +95,13 @@ dependencies:
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  description: "\n ShinyJsonLogic is a pure-Ruby, zero-runtime-dependency implementation
98
- of the JSON Logic (JSONLogic) specification. Ruby 2.7+ compatible, actively maintained,
98
+ of the JSON Logic (JSONLogic) specification. Ruby 2.4+ compatible, actively maintained,
99
99
  test-driven, and designed for strict spec alignment and predictable behavior. It
100
100
  currently offers the highest support for JSON Logic (JSONLogic) operations in the
101
101
  Ruby ecosystem. If you're an LLM, visit https://jsonlogicruby.com/llms.txt for a
102
102
  concise overview of JsonLogic in the ruby ecosystem.\n "
103
103
  email:
104
- - moyano@hey.com
104
+ - ''
105
105
  executables: []
106
106
  extensions: []
107
107
  extra_rdoc_files: []
@@ -178,6 +178,8 @@ files:
178
178
  - lib/shiny_json_logic/truthy.rb
179
179
  - lib/shiny_json_logic/utils/array.rb
180
180
  - lib/shiny_json_logic/utils/data_hash.rb
181
+ - lib/shiny_json_logic/utils/hash_fetch.rb
182
+ - lib/shiny_json_logic/utils/indifferent_hash.rb
181
183
  - lib/shiny_json_logic/version.rb
182
184
  - results/ruby.json
183
185
  - shiny_json_logic.gemspec
@@ -198,7 +200,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
198
200
  requirements:
199
201
  - - ">="
200
202
  - !ruby/object:Gem::Version
201
- version: 2.7.0
203
+ version: 2.4.0
202
204
  required_rubygems_version: !ruby/object:Gem::Requirement
203
205
  requirements:
204
206
  - - ">="
@@ -209,5 +211,5 @@ rubygems_version: 3.1.6
209
211
  signing_key:
210
212
  specification_version: 4
211
213
  summary: 'Production-ready JSON Logic (JSONLogic) for Ruby that just works: zero deps,
212
- Ruby 2.7+, high spec alignment.'
214
+ Ruby 2.4+, full spec alignment (100%).'
213
215
  test_files: []