Skip to content

Commit e6c9453

Browse files
Do not serialize nil on GETs (stripe#1786)
1 parent d7315e3 commit e6c9453

3 files changed

Lines changed: 119 additions & 6 deletions

File tree

lib/stripe/api_requestor.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -471,17 +471,17 @@ def self.maybe_gc_connection_managers
471471
raise ArgumentError, "api_base cannot be empty" if base_url.nil? || base_url.empty?
472472

473473
api_key ||= opts[:api_key]
474-
params = Util.objects_to_ids(params)
475474

476475
check_api_key!(api_key)
477476

478477
body_params = nil
479478
query_params = nil
480479
case method
481480
when :get, :head, :delete
482-
query_params = params
481+
query_params = Util.objects_to_ids(params)
483482
else
484-
body_params = params
483+
# For v2 body params, serialize nil values to preserve explicit nulls
484+
body_params = Util.objects_to_ids(params, serialize_empty: api_mode == :v2)
485485
end
486486

487487
query_params, path = merge_query_params(query_params, path)

lib/stripe/util.rb

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,22 @@ module Util
77
LEGAL_FIRST_CHARACTER = /[a-zA-Z_]/.freeze
88
LEGAL_VARIABLE_CHARACTER = /[a-zA-Z0-9_]/.freeze
99

10-
def self.objects_to_ids(obj)
10+
def self.objects_to_ids(obj, serialize_empty: false)
1111
case obj
1212
when APIResource
1313
obj.id
1414
when Hash
1515
res = {}
16-
obj.each { |k, v| res[k] = objects_to_ids(v) unless v.nil? }
16+
obj.each do |k, v|
17+
if !v.nil?
18+
res[k] = objects_to_ids(v, serialize_empty: serialize_empty)
19+
elsif serialize_empty
20+
res[k] = nil
21+
end
22+
end
1723
res
1824
when Array
19-
obj.map { |v| objects_to_ids(v) }
25+
obj.map { |v| objects_to_ids(v, serialize_empty: serialize_empty) }
2026
else
2127
obj
2228
end

test/stripe/util_test.rb

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,113 @@ class UtilTest < Test::Unit::TestCase
158158
assert_equal [1, 2, 3], obj
159159
end
160160

161+
context "#objects_to_ids" do
162+
should "convert APIResource to id" do
163+
resource = Stripe::Charge.construct_from(id: "ch_123", object: "charge")
164+
result = Util.objects_to_ids(resource)
165+
assert_equal "ch_123", result
166+
end
167+
168+
should "pass through primitives unchanged" do
169+
assert_equal "string", Util.objects_to_ids("string")
170+
assert_equal 123, Util.objects_to_ids(123)
171+
assert_equal true, Util.objects_to_ids(true)
172+
end
173+
174+
should "skip nil values in hashes by default" do
175+
input = { a: "value", b: nil, c: "another" }
176+
result = Util.objects_to_ids(input)
177+
assert_equal({ a: "value", c: "another" }, result)
178+
refute result.key?(:b)
179+
end
180+
181+
should "keep nil values in hashes when serialize_empty is true" do
182+
input = { a: "value", b: nil, c: "another" }
183+
result = Util.objects_to_ids(input, serialize_empty: true)
184+
assert_equal({ a: "value", b: nil, c: "another" }, result)
185+
assert result.key?(:b)
186+
assert_nil result[:b]
187+
end
188+
189+
should "recurse on non-nil hash values" do
190+
resource = Stripe::Charge.construct_from(id: "ch_123", object: "charge")
191+
input = { charge: resource, amount: 100 }
192+
result = Util.objects_to_ids(input)
193+
assert_equal({ charge: "ch_123", amount: 100 }, result)
194+
end
195+
196+
should "handle nested hashes with nil values by default" do
197+
resource = Stripe::Charge.construct_from(id: "ch_123", object: "charge")
198+
input = {
199+
charge: resource,
200+
metadata: { key: "value", empty: nil },
201+
description: nil,
202+
}
203+
result = Util.objects_to_ids(input)
204+
expected = {
205+
charge: "ch_123",
206+
metadata: { key: "value" },
207+
}
208+
assert_equal expected, result
209+
refute result[:metadata].key?(:empty)
210+
refute result.key?(:description)
211+
end
212+
213+
should "handle nested hashes with nil values when serialize_empty is true" do
214+
resource = Stripe::Charge.construct_from(id: "ch_123", object: "charge")
215+
input = {
216+
charge: resource,
217+
metadata: { key: "value", empty: nil },
218+
description: nil,
219+
}
220+
result = Util.objects_to_ids(input, serialize_empty: true)
221+
expected = {
222+
charge: "ch_123",
223+
metadata: { key: "value", empty: nil },
224+
description: nil,
225+
}
226+
assert_equal expected, result
227+
assert result[:metadata].key?(:empty)
228+
assert result.key?(:description)
229+
end
230+
231+
should "process arrays" do
232+
resource1 = Stripe::Charge.construct_from(id: "ch_123", object: "charge")
233+
resource2 = Stripe::Charge.construct_from(id: "ch_456", object: "charge")
234+
input = [resource1, "string", resource2]
235+
result = Util.objects_to_ids(input)
236+
assert_equal %w[ch_123 string ch_456], result
237+
end
238+
239+
should "handle complex nested structures by default" do
240+
resource = Stripe::Charge.construct_from(id: "ch_123", object: "charge")
241+
input = {
242+
charges: [resource, nil],
243+
metadata: { key: nil, nested: { value: "test", empty: nil } },
244+
}
245+
result = Util.objects_to_ids(input)
246+
expected = {
247+
charges: ["ch_123", nil],
248+
metadata: { nested: { value: "test" } },
249+
}
250+
assert_equal expected, result
251+
end
252+
253+
should "handle complex nested structures when serialize_empty is true" do
254+
resource = Stripe::Charge.construct_from(id: "ch_123", object: "charge")
255+
input = {
256+
charges: [resource, nil],
257+
metadata: { key: nil, nested: { value: "test", empty: nil } },
258+
}
259+
result = Util.objects_to_ids(input, serialize_empty: true)
260+
expected = {
261+
charges: ["ch_123", nil],
262+
metadata: { key: nil, nested: { value: "test", empty: nil } },
263+
}
264+
assert_equal expected, result
265+
end
266+
end
267+
161268
context ".request_id_dashboard_url" do
162269
should "generate a livemode URL" do
163270
assert_equal "https://dashboard.stripe.com/live/logs/request-id",

0 commit comments

Comments
 (0)