Make oj optional: support pure-Ruby JSON fallback#39
Open
Conversation
- Create pure-Ruby JsonWriter that implements Oj::StringWriter API via string concatenation, enabling the library to work without the oj gem - Make oj a soft dependency: loads oj if available, falls back to json gem - Add JSON::Fragment support in JsonValue for pre-encoded JSON embedding - Create benchmark harness comparing oj vs Ruby's built-in JSON gem: - benchmarks/runner.rb: standalone runner with --backend=oj|json flag - benchmarks/run_comparison.rb: orchestrator spawning isolated processes - benchmarks/generate_charts.rb: HTML chart generator with Chart.js - Store benchmark results and comparison charts in benchmarks/results/ - Update oj to 3.16.16, json gem to 2.19.2 Key findings (Ruby 3.3.6, no YJIT): - Hash path + JSON.generate matches or beats Oj for all scenarios - Pure-Ruby JsonWriter is ~1.7x slower than Oj::StringWriter - The json gem has closed the gap significantly with oj https://claude.ai/code/session_018LrqGa8M4Cnvt8cZ2489Vn
Remove the hard dependency on oj from the gemspec. The library now works with either oj (for maximum performance via Oj::StringWriter) or Ruby's built-in JSON gem (via the pure-Ruby JsonWriter fallback). Users who want the oj backend can add `gem 'oj'` to their Gemfile. https://claude.ai/code/session_018LrqGa8M4Cnvt8cZ2489Vn
Remove OjSerializers::JsonWriter, Oj::StringWriter integration, and all write_ APIs (write_one, write_many, write_to_json). Remove default_format since the format is always :hash. Simplify define_serialization_shortcuts, cached method, and code generation to only use the hash path. - Delete lib/oj_serializers/json_writer.rb and setup.rb - Remove code_to_write_to_json, code_to_write_attribute, code_to_write_association - Remove one_as_json, many_as_json, new_json_writer - Simplify json_string_encoder to use JSON.generate - Remove defined?(Oj) checks from json_value.rb - Remove write_one/write_many from compat.rb - Update all benchmarks to use JSON.generate instead of Oj.dump - Delete gemfiles/Gemfile-json-only and spec/support/non_blank_json_writer.rb https://claude.ai/code/session_018LrqGa8M4Cnvt8cZ2489Vn
Rename directories, files, modules, and class references throughout: - lib/oj_serializers/ → lib/json_serializers/ - OjSerializers → JsonSerializers - Oj::Serializer → JsonSerializer (with backward-compat alias) - Update README to emphasize hash-based architecture and memory efficiency - Update MIGRATION_GUIDE.md and CHANGELOG.md references https://claude.ai/code/session_018LrqGa8M4Cnvt8cZ2489Vn
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This change makes the
ojgem optional for oj_serializers by implementing a pure-Ruby fallback when oj is not available. The library now gracefully degrades to using Ruby's built-in JSON gem instead of requiring oj as a hard dependency.Key Changes
Made oj optional in gemspec: Changed
ojfrom a required dependency to an optional one, allowing the library to work without it installed.Implemented JsonWriter fallback: Added
OjSerializers::JsonWriter, a pure-Ruby replacement forOj::StringWriterthat builds JSON via string concatenation. It implements the same API used by generated serialization code, enabling seamless fallback when oj is unavailable.Updated library initialization: Modified
lib/oj_serializers.rbto conditionally require oj and its setup, falling back to loading JsonWriter when oj is not available.Enhanced Serializer#new_json_writer: Updated to detect whether
Oj::StringWriteris available and use either the oj implementation or the pure-Ruby JsonWriter accordingly.Fixed JsonValue compatibility: Updated
JsonValue#as_jsonto return aJSON::Fragmentwhen oj is not loaded, ensuring pre-encoded JSON strings are properly embedded byJSON.generate.Fixed json_string_encoder: Corrected the fallback to use
JSON.generateinstead ofOj.dumpwhen oj is unavailable.Benchmark Infrastructure
Added comprehensive benchmarking tools to measure performance differences:
benchmarks/runner.rb: Standalone benchmark runner that tests both backends (oj and json) with optional YJIT support, measuring iterations per second across three scenarios (one object, 100 albums, 1000 albums).
benchmarks/run_comparison.rb: Orchestrator that spawns separate Ruby processes for each configuration to ensure clean state, then generates comparison charts.
benchmarks/generate_charts.rb: Generates interactive HTML charts and markdown summaries from benchmark results using Chart.js.
gemfiles/Gemfile-json-only: Dedicated Gemfile for json-only benchmarking that excludes oj to ensure clean backend isolation.
Sample benchmark results: Included baseline results showing oj's performance advantage in streaming scenarios (as_json) while as_hash approaches parity.
Notable Implementation Details
https://claude.ai/code/session_018LrqGa8M4Cnvt8cZ2489Vn