shiny_json_logic 0.2.15 → 0.2.16
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 +6 -1
- data/Gemfile.lock +1 -1
- data/lib/shiny_json_logic/engine.rb +7 -18
- data/lib/shiny_json_logic/operations/base.rb +1 -1
- data/lib/shiny_json_logic/operations/filter.rb +1 -1
- data/lib/shiny_json_logic/operations/if.rb +1 -1
- data/lib/shiny_json_logic/operations/iterable/base.rb +1 -1
- data/lib/shiny_json_logic/operations/preserve.rb +1 -1
- data/lib/shiny_json_logic/operations/reduce.rb +2 -2
- data/lib/shiny_json_logic/operations/try.rb +1 -2
- data/lib/shiny_json_logic/operations/val.rb +3 -5
- data/lib/shiny_json_logic/operations/var.rb +2 -8
- data/lib/shiny_json_logic/scope_stack.rb +8 -19
- data/lib/shiny_json_logic/version.rb +1 -1
- data/lib/shiny_json_logic.rb +17 -3
- metadata +2 -3
- data/lib/shiny_json_logic/utils/indifferent_hash.rb +0 -72
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1a4ebb694ae1ef43b04f2ef7a750bc102f202cc2e9efa6e0f43361bc66b82d4f
|
|
4
|
+
data.tar.gz: e57b52493d8d60506c2ceb5c30bace49790bbd6fa5eb2e800f31fb4aae94bbd6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2b336cd498e0286fe1a2f953fc43a8319b83b3792f4471ff8f78eed0dd7cb9174ade63f7fba91b7accca6eccedffb785fb413eae82de9d7f67611d5a5ea64959
|
|
7
|
+
data.tar.gz: 3f76cf1ac0abea0a1e04a2b6ed2c1b468a028018a471afb20e88b4031555bdadaf0f917f179a84a0c258f4404bf9d61e7ad36a9e07c660a5ebd0177f7236e324
|
data/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
|
-
## [0.2.
|
|
4
|
+
## [0.2.16] - 2026-02-21
|
|
5
|
+
### Changed
|
|
6
|
+
- Removed simple delegator to check indifferent access in favour of key transformation . This improves performance and reduces memory usage.
|
|
7
|
+
- Removes instantiation of engine in favor of static evaluation to improve performance & memory usage.
|
|
8
|
+
|
|
9
|
+
## [0.2.15] - 2026-02-20
|
|
5
10
|
### Changed
|
|
6
11
|
- Refactors operation solvers to improve performance.
|
|
7
12
|
- Includes frozen string literals to optimize string handling.
|
data/Gemfile.lock
CHANGED
|
@@ -3,35 +3,24 @@
|
|
|
3
3
|
require "shiny_json_logic/operator_solver"
|
|
4
4
|
|
|
5
5
|
module ShinyJsonLogic
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
@rule = rule
|
|
9
|
-
@scope_stack = scope_stack
|
|
10
|
-
end
|
|
6
|
+
module Engine
|
|
7
|
+
OPERATIONS = OperatorSolver::SOLVERS
|
|
11
8
|
|
|
12
|
-
def call(rule
|
|
9
|
+
def self.call(rule, scope_stack)
|
|
13
10
|
if rule.is_a?(Hash)
|
|
14
11
|
return rule if rule.empty?
|
|
15
12
|
|
|
16
13
|
operation, args = rule.to_a.first
|
|
17
14
|
operation_key = operation.to_s
|
|
18
15
|
|
|
19
|
-
return rule unless
|
|
16
|
+
return rule unless OPERATIONS.key?(operation_key)
|
|
20
17
|
|
|
21
|
-
|
|
18
|
+
OPERATIONS.fetch(operation_key).new(args, scope_stack).call
|
|
22
19
|
elsif rule.is_a?(Array)
|
|
23
|
-
rule.map { |val| call(val) }
|
|
20
|
+
rule.map { |val| call(val, scope_stack) }
|
|
24
21
|
else
|
|
25
22
|
rule
|
|
26
23
|
end
|
|
27
24
|
end
|
|
28
|
-
|
|
29
|
-
private
|
|
30
|
-
|
|
31
|
-
attr_reader :rule, :scope_stack
|
|
32
|
-
|
|
33
|
-
def operations
|
|
34
|
-
OperatorSolver::SOLVERS
|
|
35
|
-
end
|
|
36
25
|
end
|
|
37
|
-
end
|
|
26
|
+
end
|
|
@@ -15,7 +15,7 @@ module ShinyJsonLogic
|
|
|
15
15
|
initial_accumulator_rule = rules.is_a?(Array) ? rules[2] : nil
|
|
16
16
|
super
|
|
17
17
|
# Evaluate the initial accumulator value (third argument)
|
|
18
|
-
@accumulator = Engine.
|
|
18
|
+
@accumulator = Engine.call(initial_accumulator_rule, scope_stack)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
private
|
|
@@ -33,7 +33,7 @@ module ShinyJsonLogic
|
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
def on_each(_item)
|
|
36
|
-
self.accumulator = Engine.
|
|
36
|
+
self.accumulator = Engine.call(filter, scope_stack)
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def on_after(_results)
|
|
@@ -46,8 +46,9 @@ module ShinyJsonLogic
|
|
|
46
46
|
keys.reduce(data) do |obj, key|
|
|
47
47
|
return nil if obj.nil?
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
if obj.is_a?(Hash)
|
|
50
|
+
# Normalize key to string for lookup (data is already string-keyed)
|
|
51
|
+
obj[key.to_s]
|
|
51
52
|
elsif obj.is_a?(Array)
|
|
52
53
|
# Convert string keys to integers for arrays
|
|
53
54
|
index = key.is_a?(String) ? key.to_i : key
|
|
@@ -55,9 +56,6 @@ module ShinyJsonLogic
|
|
|
55
56
|
else
|
|
56
57
|
nil
|
|
57
58
|
end
|
|
58
|
-
|
|
59
|
-
# Wrap nested hashes for indifferent access
|
|
60
|
-
result.is_a?(Hash) && !result.is_a?(IndifferentHash) ? IndifferentHash.new(result) : result
|
|
61
59
|
end
|
|
62
60
|
end
|
|
63
61
|
end
|
|
@@ -31,14 +31,8 @@ module ShinyJsonLogic
|
|
|
31
31
|
return nil if current.nil?
|
|
32
32
|
|
|
33
33
|
if current.is_a?(Hash)
|
|
34
|
-
#
|
|
35
|
-
|
|
36
|
-
current[k]
|
|
37
|
-
elsif current.key?(k.to_sym)
|
|
38
|
-
current[k.to_sym]
|
|
39
|
-
else
|
|
40
|
-
nil
|
|
41
|
-
end
|
|
34
|
+
# Data is already normalized to string keys
|
|
35
|
+
current[k]
|
|
42
36
|
elsif current.is_a?(Array)
|
|
43
37
|
current[k.to_i]
|
|
44
38
|
else
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "shiny_json_logic/utils/indifferent_hash"
|
|
4
|
-
|
|
5
3
|
module ShinyJsonLogic
|
|
6
4
|
# Manages a stack of scopes for nested data access in iterators.
|
|
7
5
|
#
|
|
@@ -15,17 +13,20 @@ module ShinyJsonLogic
|
|
|
15
13
|
# {"val": [[1], "key"]} -> go up 1 level, access "key"
|
|
16
14
|
# {"val": [[2], "key"]} -> go up 2 levels, access "key"
|
|
17
15
|
#
|
|
16
|
+
# Note: Data is normalized to string keys upfront in ShinyJsonLogic.apply,
|
|
17
|
+
# so no indifferent access is needed here.
|
|
18
|
+
#
|
|
18
19
|
class ScopeStack
|
|
19
20
|
attr_reader :stack
|
|
20
21
|
|
|
21
22
|
def initialize(root_data)
|
|
22
|
-
@root_data =
|
|
23
|
+
@root_data = root_data
|
|
23
24
|
@stack = [{ data: @root_data, index: 0 }]
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
# Push a new scope onto the stack (when entering an iteration)
|
|
27
28
|
def push(data, index: 0)
|
|
28
|
-
stack.push({ data:
|
|
29
|
+
stack.push({ data: data, index: index })
|
|
29
30
|
end
|
|
30
31
|
|
|
31
32
|
# Pop the top scope (when exiting an iteration)
|
|
@@ -67,8 +68,9 @@ module ShinyJsonLogic
|
|
|
67
68
|
keys.reduce(data) do |obj, key|
|
|
68
69
|
return nil if obj.nil?
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
if obj.is_a?(Hash)
|
|
72
|
+
# Normalize key to string for lookup
|
|
73
|
+
obj[key.to_s]
|
|
72
74
|
elsif obj.is_a?(Array)
|
|
73
75
|
# Convert string keys to integers for arrays
|
|
74
76
|
index = key.is_a?(String) ? key.to_i : key
|
|
@@ -76,19 +78,6 @@ module ShinyJsonLogic
|
|
|
76
78
|
else
|
|
77
79
|
nil
|
|
78
80
|
end
|
|
79
|
-
|
|
80
|
-
# Wrap nested hashes for indifferent access
|
|
81
|
-
result.is_a?(Hash) && !result.is_a?(IndifferentHash) ? IndifferentHash.new(result) : result
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def wrap_indifferent(obj)
|
|
86
|
-
if obj.is_a?(IndifferentHash)
|
|
87
|
-
obj
|
|
88
|
-
elsif obj.is_a?(Hash)
|
|
89
|
-
IndifferentHash.new(obj)
|
|
90
|
-
else
|
|
91
|
-
obj
|
|
92
81
|
end
|
|
93
82
|
end
|
|
94
83
|
end
|
data/lib/shiny_json_logic.rb
CHANGED
|
@@ -13,9 +13,23 @@ module ShinyJsonLogic
|
|
|
13
13
|
def self.apply(rule, data = {})
|
|
14
14
|
validate_operators!(rule)
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
normalized_data = deep_stringify_keys(data || {})
|
|
17
|
+
scope_stack = ScopeStack.new(normalized_data)
|
|
18
|
+
Engine.call(rule, scope_stack)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Recursively converts all hash keys to strings
|
|
22
|
+
def self.deep_stringify_keys(obj)
|
|
23
|
+
case obj
|
|
24
|
+
when Hash
|
|
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
|
|
19
33
|
end
|
|
20
34
|
|
|
21
35
|
# Validates that all operations in the rule tree use known operators
|
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.2.
|
|
4
|
+
version: 0.2.16
|
|
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-
|
|
11
|
+
date: 2026-02-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -177,7 +177,6 @@ files:
|
|
|
177
177
|
- lib/shiny_json_logic/scope_stack.rb
|
|
178
178
|
- lib/shiny_json_logic/truthy.rb
|
|
179
179
|
- lib/shiny_json_logic/utils/array.rb
|
|
180
|
-
- lib/shiny_json_logic/utils/indifferent_hash.rb
|
|
181
180
|
- lib/shiny_json_logic/version.rb
|
|
182
181
|
- results/ruby.json
|
|
183
182
|
- shiny_json_logic.gemspec
|
|
@@ -1,72 +0,0 @@
|
|
|
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
|