From a61f033ded2bb2a3d77d520173a323d6401a191e Mon Sep 17 00:00:00 2001 From: Josh Wilson Date: Fri, 3 Jul 2026 17:03:48 -0500 Subject: [PATCH] Fix serializer crash when multi_json is loaded without RubyGems activation Since 8.5.2, `Serializer::MultiJson#deprecated_gem_version_loaded?` decides between the deprecated `MultiJson` API and the new `MultiJSON` API by consulting `Gem.loaded_specs['multi_json'].version`. When the gem is loaded from a plain $LOAD_PATH without RubyGems activation -- e.g. from a `bundle install --standalone` bundle or a vendored load path -- `Gem.loaded_specs` is empty, so every request fails with: NoMethodError: undefined method 'version' for nil Detect the API by checking for the `MultiJSON` constant instead: multi_json 1.21.0 is exactly the version that introduced that constant, so the check is equivalent, and it works regardless of how the gem was loaded. --- .../transport/serializer/multi_json.rb | 8 +++++++- test/unit/serializer_test.rb | 17 +++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/lib/elastic/transport/transport/serializer/multi_json.rb b/lib/elastic/transport/transport/serializer/multi_json.rb index 1da59314..551ac545 100644 --- a/lib/elastic/transport/transport/serializer/multi_json.rb +++ b/lib/elastic/transport/transport/serializer/multi_json.rb @@ -56,8 +56,14 @@ def dump(object, options = {}) private + # multi_json 1.21.0 renamed the top-level constant to `MultiJSON` (keeping a + # deprecated `MultiJson` alias), so the constant's absence indicates a deprecated + # version. Checking the constant rather than `Gem.loaded_specs` also works when + # the gem is loaded without RubyGems activation (e.g. from a `bundle install + # --standalone` bundle or a vendored load path), where `Gem.loaded_specs` is + # empty and the version lookup would raise a `NoMethodError` on `nil`. def deprecated_gem_version_loaded? - Gem.loaded_specs['multi_json'].version < Gem::Version.create('1.21.0') + !defined?(::MultiJSON) end end end diff --git a/test/unit/serializer_test.rb b/test/unit/serializer_test.rb index c65c3be3..bfb24fa1 100644 --- a/test/unit/serializer_test.rb +++ b/test/unit/serializer_test.rb @@ -22,18 +22,27 @@ class Elastic::Transport::Transport::SerializerTest < Minitest::Test context "Serializer" do should "use MultiJSON by default" do - if Gem.loaded_specs['multi_json'].version < Gem::Version.create('1.21.0') - ::MultiJson.expects(:load) - ::MultiJson.expects(:dump) - else + if defined?(::MultiJSON) ::MultiJSON.expects(:parse) ::MultiJSON.expects(:generate) + else + ::MultiJson.expects(:load) + ::MultiJson.expects(:dump) end Elastic::Transport::Transport::Serializer::MultiJson.new.load('{}') Elastic::Transport::Transport::Serializer::MultiJson.new.dump({}) end + should "work when multi_json is loaded without RubyGems activation" do + # When gems are loaded from a plain $LOAD_PATH (e.g. a `bundle install --standalone` + # bundle or a vendored load path), they are not registered in Gem.loaded_specs. + Gem.stubs(:loaded_specs).returns({}) + + assert_equal({ 'foo' => 'bar' }, Elastic::Transport::Transport::Serializer::MultiJson.new.load('{"foo":"bar"}')) + assert_equal('{"foo":"bar"}', Elastic::Transport::Transport::Serializer::MultiJson.new.dump({ 'foo' => 'bar' })) + end + end end