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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9d1f6e3df479eeb12b69af816742069bd38c31953c3b1611add83bc3aac4434a
4
- data.tar.gz: 69b8a9a12b7a95fcf12ac173c6559d9e002a9d8d085c05b28716eb9948262fe5
3
+ metadata.gz: f593dd25616efcfc78e8efbda2f1909d8ba63d19e1b144c9d9708c7a9e736fde
4
+ data.tar.gz: 83c8f2bc4b1dfdc7da601cb21739ae2b2ff5c22964d0cefeae04cb6bcae4d09a
5
5
  SHA512:
6
- metadata.gz: c603bf6cd3c2a58f248b1ca535ebcd63d28f3d1cdc269ff73f7e2889f161316c0a1c3a710c7e250c5529d090458fc42ffeac1cb2a43020fb81d037b500548a04
7
- data.tar.gz: 12a21c296a86fe78d87ffd71ad9bba9b81200d60c8ffb1f3959e77f56f7312d31fda4d04fc00e3baa37103dee548237d0827810d8abe672b148b696d86f9c9e4
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
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Philiprehberger
4
4
  module JsonPath
5
- VERSION = '0.4.0'
5
+ VERSION = '0.5.0'
6
6
  end
7
7
  end
@@ -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.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-04-20 00:00:00.000000000 Z
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,