philiprehberger-json_path 0.4.0 → 0.5.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 +5 -0
- data/README.md +15 -0
- data/lib/philiprehberger/json_path/version.rb +1 -1
- data/lib/philiprehberger/json_path.rb +73 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f593dd25616efcfc78e8efbda2f1909d8ba63d19e1b144c9d9708c7a9e736fde
|
|
4
|
+
data.tar.gz: 83c8f2bc4b1dfdc7da601cb21739ae2b2ff5c22964d0cefeae04cb6bcae4d09a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 63eaa887ffe7f5a8e440b06e78217c9f19391dafd3705ac0fb29a68fbd6362599532626f2c47f9848d76e83370bc3a38503942a8e48d3fbf4cd332adef9813e4
|
|
7
|
+
data.tar.gz: 86c79db3e059ae57cd8192fa63168aa58b34a6a187041a3ce7905a58276a30fd0c10b7289da3724ec122d49283011c555f9b557b3e916bee3f91a168f05217fe
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.0] - 2026-05-01
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `JsonPath.update(data, path, &block)` — apply a block to every JSONPath match, replacing each with the block's return value. Mutates `data` in place; returns it for chaining. Raises when the path resolves to the root document.
|
|
14
|
+
|
|
10
15
|
## [0.4.0] - 2026-04-19
|
|
11
16
|
|
|
12
17
|
### Added
|
data/README.md
CHANGED
|
@@ -130,6 +130,20 @@ Philiprehberger::JsonPath.query(data, '$.groups[?(@.members.length > 0)].name')
|
|
|
130
130
|
# => ["team1"]
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
+
### In-place Updates
|
|
134
|
+
|
|
135
|
+
Apply a transformation to every match. Mutates the data and returns it for chaining.
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
data = { 'users' => [{ 'name' => 'alice' }, { 'name' => 'bob' }] }
|
|
139
|
+
|
|
140
|
+
Philiprehberger::JsonPath.update(data, '$.users[*].name', &:upcase)
|
|
141
|
+
# data["users"].map { |u| u["name"] } => ["ALICE", "BOB"]
|
|
142
|
+
|
|
143
|
+
# Increment every numeric price in a nested store
|
|
144
|
+
Philiprehberger::JsonPath.update(catalog, '$..price') { |p| p * 1.10 }
|
|
145
|
+
```
|
|
146
|
+
|
|
133
147
|
### Supported Syntax
|
|
134
148
|
|
|
135
149
|
| Syntax | Description |
|
|
@@ -157,6 +171,7 @@ Philiprehberger::JsonPath.query(data, '$.groups[?(@.members.length > 0)].name')
|
|
|
157
171
|
| `JsonPath.count(data, path)` | Return the number of matches |
|
|
158
172
|
| `JsonPath.exists?(data, path)` | Check if any match exists |
|
|
159
173
|
| `JsonPath.paths(data, path)` | Return canonical JSONPath strings for each match |
|
|
174
|
+
| `JsonPath.update(data, path, &block)` | Replace every matched value with the block's return; mutates `data` in place |
|
|
160
175
|
|
|
161
176
|
## Development
|
|
162
177
|
|
|
@@ -75,6 +75,79 @@ module Philiprehberger
|
|
|
75
75
|
evaluate_with_paths(data, tokens).map { |_node, p| p }
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
+
# Apply a transformation to every JSONPath match in `data`.
|
|
79
|
+
#
|
|
80
|
+
# Mutates `data` in place and returns it for chaining. Each match is
|
|
81
|
+
# replaced with the block's return value. Skips matches that resolve
|
|
82
|
+
# to the document root (`"$"`). Use `Marshal.load(Marshal.dump(data))`
|
|
83
|
+
# first if you need to preserve the original.
|
|
84
|
+
#
|
|
85
|
+
# @param data [Hash, Array] the data to transform
|
|
86
|
+
# @param path [String] JSONPath expression
|
|
87
|
+
# @yield [value] each matched value
|
|
88
|
+
# @yieldreturn [Object] the replacement value
|
|
89
|
+
# @return [Hash, Array] the mutated data
|
|
90
|
+
# @raise [Error] when the path resolves to the root document
|
|
91
|
+
def self.update(data, path, &block)
|
|
92
|
+
raise Error, 'block required for update' unless block
|
|
93
|
+
|
|
94
|
+
paths(data, path).each do |canonical|
|
|
95
|
+
raise Error, 'Cannot update root document' if canonical == '$'
|
|
96
|
+
|
|
97
|
+
mutate_at(data, canonical, &block)
|
|
98
|
+
end
|
|
99
|
+
data
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# @api private
|
|
103
|
+
def self.mutate_at(data, canonical, &block)
|
|
104
|
+
segments = canonical_segments(canonical)
|
|
105
|
+
*parent_path, last = segments
|
|
106
|
+
parent = parent_path.reduce(data) { |node, seg| descend(node, seg) }
|
|
107
|
+
|
|
108
|
+
if parent.is_a?(Hash)
|
|
109
|
+
key = parent.key?(last) ? last : last.to_sym
|
|
110
|
+
parent[key] = block.call(parent[key])
|
|
111
|
+
elsif parent.is_a?(Array)
|
|
112
|
+
idx = Integer(last)
|
|
113
|
+
parent[idx] = block.call(parent[idx])
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# @api private
|
|
118
|
+
def self.canonical_segments(canonical)
|
|
119
|
+
remaining = canonical[1..]
|
|
120
|
+
segments = []
|
|
121
|
+
|
|
122
|
+
until remaining.empty?
|
|
123
|
+
case remaining
|
|
124
|
+
when /\A\.([A-Za-z_][A-Za-z0-9_]*)/
|
|
125
|
+
segments << ::Regexp.last_match(1)
|
|
126
|
+
remaining = remaining[::Regexp.last_match(0).length..]
|
|
127
|
+
when /\A\['((?:\\'|[^'])*)'\]/
|
|
128
|
+
segments << ::Regexp.last_match(1).gsub("\\'", "'")
|
|
129
|
+
remaining = remaining[::Regexp.last_match(0).length..]
|
|
130
|
+
when /\A\[(\d+)\]/
|
|
131
|
+
segments << ::Regexp.last_match(1)
|
|
132
|
+
remaining = remaining[::Regexp.last_match(0).length..]
|
|
133
|
+
else
|
|
134
|
+
raise Error, "Unparseable canonical path segment: #{remaining}"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
segments
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# @api private
|
|
142
|
+
def self.descend(node, segment)
|
|
143
|
+
if node.is_a?(Hash)
|
|
144
|
+
node.key?(segment) ? node[segment] : node[segment.to_sym]
|
|
145
|
+
elsif node.is_a?(Array)
|
|
146
|
+
node[Integer(segment)]
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
private_class_method :mutate_at, :canonical_segments, :descend
|
|
150
|
+
|
|
78
151
|
class << self
|
|
79
152
|
private
|
|
80
153
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: philiprehberger-json_path
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- philiprehberger
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-05-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Evaluate JSONPath expressions against Ruby hashes and arrays. Supports
|
|
14
14
|
dot notation, array indexing, wildcards, slices, filter expressions, recursive descent,
|