diff --git a/.gitignore b/.gitignore
index 8c4389d..5a0f241 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@
/pkg/
/spec/reports/
/tmp/
+/log/
/benchmark/flamegraphs/
/gemfiles/*.lock
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index f837b9d..0830495 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -19,7 +19,7 @@ Bundler/OrderedGems:
# Include: **/*.gemspec
Gemspec/RequiredRubyVersion:
Exclude:
- - 'oj_serializers.gemspec'
+ - 'json_serializers.gemspec'
# Offense count: 5
# This cop supports safe autocorrection (--autocorrect).
@@ -44,21 +44,21 @@ Lint/RedundantDirGlobSort:
# SupportedStyles: strict, consistent
Lint/SymbolConversion:
Exclude:
- - 'lib/oj_serializers/serializer.rb'
+ - 'lib/json_serializers/serializer.rb'
# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: AutoCorrect, IgnoreEmptyBlocks, AllowUnusedKeywordArguments.
Lint/UnusedBlockArgument:
Exclude:
- - 'lib/oj_serializers/serializer.rb'
+ - 'lib/json_serializers/serializer.rb'
# Offense count: 2
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: AllowedReceivers.
Lint/UselessDefaultValueArgument:
Exclude:
- - 'lib/oj_serializers/serializer.rb'
+ - 'lib/json_serializers/serializer.rb'
# Offense count: 5
# This cop supports safe autocorrection (--autocorrect).
@@ -72,7 +72,7 @@ Style/StringLiterals:
# This cop supports safe autocorrection (--autocorrect).
Style/SuperArguments:
Exclude:
- - 'lib/oj_serializers/serializer.rb'
+ - 'lib/json_serializers/serializer.rb'
# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
@@ -88,7 +88,7 @@ Style/SymbolProc:
# SupportedStylesForMultiline: comma, consistent_comma, no_comma
Style/TrailingCommaInArguments:
Exclude:
- - 'spec/oj_serializers/sort_attributes_spec.rb'
+ - 'spec/json_serializers/sort_attributes_spec.rb'
# Offense count: 17
# This cop supports safe autocorrection (--autocorrect).
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0a191eb..b5fa011 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,16 +1,16 @@
-## Oj Serializers 3.0.0 (2026-01-02)
+## JSON Serializers 3.0.0 (2026-01-02)
### Features β¨
- [Pass user options to children associations](https://github.com/ElMassimo/oj_serializers/commit/d495f06)
-## Oj Serializers 2.1.0 (2026-01-02)
+## JSON Serializers 2.1.0 (2026-01-02)
### Fixes π
- [Improve sorting by :definition with more than 10 attributes in the list](https://github.com/ElMassimo/oj_serializers/commit/d58cb81) (#25)
-## Oj Serializers 2.0.3 (2023-04-19)
+## JSON Serializers 2.0.3 (2023-04-19)
### Features β¨
@@ -20,7 +20,7 @@
- [Allow using `active_model_serializers` in associations](https://github.com/ElMassimo/oj_serializers/commit/501ed4014b564e6f103d2f52d15832fe6706d6a8)
-## Oj Serializers 2.0.2 (2023-04-02)
+## JSON Serializers 2.0.2 (2023-04-02)
### Features β¨
@@ -30,7 +30,7 @@
- [Error when defining attributes with options](https://github.com/ElMassimo/oj_serializers/commit/680ab47)
-## Oj Serializers 2.0.1 (2023-04-02)
+## JSON Serializers 2.0.1 (2023-04-02)
### Features β¨
@@ -41,7 +41,7 @@
- [Aliased attributes should be sorted by the output key](https://github.com/ElMassimo/oj_serializers/commit/fc6f4c1)
-## [Oj Serializers 2.0.0 (2023-03-27)](https://github.com/ElMassimo/oj_serializers/pull/9)
+## [JSON Serializers 2.0.0 (2023-03-27)](https://github.com/ElMassimo/oj_serializers/pull/9)
### Features β¨
@@ -54,20 +54,18 @@
### Breaking Changes
-Since returning a `Hash` is more convenient than returning a `Oj::StringWriter`, and performance is comparable, `default_format :hash` is now the default.
+Since returning a `Hash` is more convenient and performance is comparable, `default_format :hash` is now the default.
-The previous APIs will still be available as `one_as_json` and `many_as_json`, as well as `default_format :json` to make the library work like in version 1.
-
-## Oj Serializers 1.0.2 (2023-03-01) ##
+## JSON Serializers 1.0.2 (2023-03-01) ##
* [fix: avoid freezing `ALLOWED_INSTANCE_VARIABLES`](https://github.com/ElMassimo/oj_serializers/commit/ade0302)
-## Oj Serializers 1.0.1 (2023-03-01) ##
+## JSON Serializers 1.0.1 (2023-03-01) ##
* [fix: avoid caching instances of reloaded classes in development](https://github.com/ElMassimo/oj_serializers/commit/0bd928d64d159926acf6b4d57e3f08b12f6931ce)
-## Oj Serializers 1.0.0 (2020-11-05) ##
+## JSON Serializers 1.0.0 (2020-11-05) ##
* Initial Release.
diff --git a/Gemfile.lock b/Gemfile.lock
index 6a1a1fe..d07dc29 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,8 +1,7 @@
PATH
remote: .
specs:
- oj_serializers (3.0.0)
- oj (>= 3.14.0)
+ json_serializers (3.0.0)
GEM
remote: https://rubygems.org/
@@ -90,7 +89,7 @@ GEM
benchmark-ips (2.14.0)
benchmark-memory (0.2.0)
memory_profiler (~> 1)
- bigdecimal (3.2.2)
+ bigdecimal (4.0.1)
blueprinter (0.30.0)
bson (5.1.1)
builder (3.3.0)
@@ -116,7 +115,7 @@ GEM
pp (>= 0.6.0)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
- json (2.12.2)
+ json (2.19.2)
jsonapi-renderer (0.2.2)
language_server-protocol (3.17.0.5)
lint_roller (1.1.0)
@@ -155,10 +154,10 @@ GEM
racc (~> 1.4)
nokogiri (1.18.8-x86_64-linux-gnu)
racc (~> 1.4)
- oj (3.16.11)
+ oj (3.16.16)
bigdecimal (>= 3.0)
ostruct (>= 0.2)
- ostruct (0.6.2)
+ ostruct (0.6.3)
panko_serializer (0.8.3)
activesupport
oj (> 3.11.0, < 4.0.0)
@@ -296,9 +295,9 @@ DEPENDENCIES
benchmark-memory
blueprinter (~> 0.8)
json
+ json_serializers!
memory_profiler
mongoid
- oj_serializers!
panko_serializer
pry-byebug (~> 3.9)
rails
@@ -310,4 +309,4 @@ DEPENDENCIES
sqlite3
BUNDLED WITH
- 2.3.22
+ 2.7.2
diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md
index b93440d..a35d200 100644
--- a/MIGRATION_GUIDE.md
+++ b/MIGRATION_GUIDE.md
@@ -5,17 +5,15 @@
[readme]: https://github.com/ElMassimo/oj_serializers/blob/main/README.md
[attributes dsl]: https://github.com/ElMassimo/oj_serializers/blob/main/README.md#attributes-dsl-
-[oj]: https://github.com/ohler55/oj
[ams]: https://github.com/rails-api/active_model_serializers
[jsonapi]: https://github.com/jsonapi-serializer/jsonapi-serializer
[panko]: https://github.com/panko-serializer/panko_serializer
[benchmarks]: https://github.com/ElMassimo/oj_serializers/tree/master/benchmarks
[raw_benchmarks]: https://github.com/ElMassimo/oj_serializers/blob/main/benchmarks/document_benchmark.rb
[migration guide]: https://github.com/ElMassimo/oj_serializers/blob/main/MIGRATION_GUIDE.md
-[raw_json]: https://github.com/ohler55/oj/issues/542
[trailing_commas]: https://maximomussini.com/posts/trailing-commas/
-The DSL of `oj_serializers` is meant to be similar to the one provided by `active_model_serializers` to make the migration process simple,
+The DSL of `json_serializers` is meant to be similar to the one provided by `active_model_serializers` to make the migration process simple,
though the goal is not to be a drop-in replacement.
## Rendering π
@@ -24,7 +22,7 @@ To use the same format in controllers, using the `root`, `serializer`, `each_ser
```ruby
# config/initializers/json.rb
-require 'oj_serializers/compat'
+require 'json_serializers/compat'
```
Otherwise, use `one` and `many` to serialize objects or enumerables:
@@ -80,7 +78,7 @@ end
# becomes
-class AlbumSerializer < Oj::Serializer
+class AlbumSerializer < JsonSerializer
ams_attributes :name, :release
# The serializer class must be explicitly provided.
@@ -102,7 +100,7 @@ Once your serializer is working as expected, you can further refactor it to be m
Being explicit about where the attributes are coming from makes the serializers easier to understand and more maintainable.
```ruby
-class AlbumSerializer < Oj::Serializer
+class AlbumSerializer < JsonSerializer
attributes :name
has_many :songs, serializer: SongSerializer
@@ -140,7 +138,7 @@ In case you need to access path helpers in your serializers, you can use the
following:
```ruby
-class BaseJsonSerializer < Oj::Serializer
+class BaseJsonSerializer < JsonSerializer
include Rails.application.routes.url_helpers
def default_url_options
@@ -165,7 +163,7 @@ class ApplicationController < ActionController::Base
before_action { Thread.current[:current_controller] = self }
end
-class BaseJsonSerializer < Oj::Serializer
+class BaseJsonSerializer < JsonSerializer
def scope
@scope ||= Thread.current[:current_controller]
end
diff --git a/README.md b/README.md
index e3177a5..2df1475 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,16 @@
-Oj Serializers
+JSON Serializers
-
+
-Faster JSON serializers for Ruby, built on top of the powerful [`oj`][oj] library.
+Fast, memory-efficient JSON serializers for Ruby and Rails.
-[oj]: https://github.com/ohler55/oj
[mongoid]: https://github.com/mongodb/mongoid
[ams]: https://github.com/rails-api/active_model_serializers
[jsonapi]: https://github.com/jsonapi-serializer/jsonapi-serializer
@@ -19,12 +18,11 @@ Faster JSON serializers for Ruby, built on top of the powerful [`oj`][oj] librar
[blueprinter]: https://github.com/procore/blueprinter
[benchmarks]: https://github.com/ElMassimo/oj_serializers/tree/master/benchmarks
[raw_benchmarks]: https://github.com/ElMassimo/oj_serializers/blob/main/benchmarks/document_benchmark.rb
-[sugar]: https://github.com/ElMassimo/oj_serializers/blob/main/lib/oj_serializers/sugar.rb#L14
+[sugar]: https://github.com/ElMassimo/oj_serializers/blob/main/lib/json_serializers/sugar.rb#L14
[migration guide]: https://github.com/ElMassimo/oj_serializers/blob/main/MIGRATION_GUIDE.md
[design]: https://github.com/ElMassimo/oj_serializers#design-
[associations]: https://github.com/ElMassimo/oj_serializers#associations-
[compose]: https://github.com/ElMassimo/oj_serializers#composing-serializers-
-[raw_json]: https://github.com/ohler55/oj/issues/542
[trailing_commas]: https://maximomussini.com/posts/trailing-commas/
[render dsl]: https://github.com/ElMassimo/oj_serializers#render-dsl-
[sorbet]: https://sorbet.org/
@@ -33,16 +31,16 @@ Faster JSON serializers for Ruby, built on top of the powerful [`oj`][oj] librar
[types_from_serializers]: https://github.com/ElMassimo/types_from_serializers
[inheritance]: https://github.com/ElMassimo/types_from_serializers/blob/main/playground/vanilla/app/serializers/song_with_videos_serializer.rb#L1
-## Why? π€
+## Why?
[`ActiveModel::Serializer`][ams] has a nice DSL, but it allocates many objects
leading to memory bloat, time spent on GC, and lower performance.
-`Oj::Serializer` provides a similar API, with [better performance][benchmarks].
+`JsonSerializer` provides a similar API, with [better performance][benchmarks].
Learn more about [how this library achieves its performance][design].
-## Features β‘οΈ
+## Features
- Intuitive declaration syntax, supporting mixins and inheritance
- Reduced [memory allocation][benchmarks] and [improved performance][benchmarks]
@@ -51,25 +49,25 @@ Learn more about [how this library achieves its performance][design].
- Useful development checks to avoid typos and mistakes
- [Migrate easily from Active Model Serializers][migration guide]
-## Installation πΏ
+## Installation
Add this line to your application's Gemfile:
```ruby
-gem 'oj_serializers'
+gem 'json_serializers'
```
And then run:
$ bundle install
-## Usage π
+## Usage
-You can define a serializer by subclassing `Oj::Serializer`, and specify which
+You can define a serializer by subclassing `JsonSerializer`, and specify which
attributes should be serialized.
```ruby
-class AlbumSerializer < Oj::Serializer
+class AlbumSerializer < JsonSerializer
attributes :name, :genres
attribute :release do
@@ -162,13 +160,13 @@ end
Active Model Serializers style
```ruby
-require "oj_serializers/sugar" # In an initializer
+require "json_serializers/sugar" # In an initializer
class AlbumsController < ApplicationController
def show
render json: album, serializer: AlbumSerializer
end
-
+
def index
render json: albums, root: :albums, each_serializer: AlbumSerializer
end
@@ -176,7 +174,7 @@ end
```
-## Rendering π¨
+## Rendering
Use `one` to serialize objects, and `many` to serialize enumerables:
@@ -200,12 +198,12 @@ render json: {
}
```
-## Attributes DSL πͺ
+## Attributes DSL
Specify which attributes should be rendered by calling a method in the object to serialize.
```ruby
-class PlayerSerializer < Oj::Serializer
+class PlayerSerializer < JsonSerializer
attributes :first_name, :last_name, :full_name
end
```
@@ -213,7 +211,7 @@ end
You can serialize custom values by specifying that a method is an `attribute`:
```ruby
-class PlayerSerializer < Oj::Serializer
+class PlayerSerializer < JsonSerializer
attribute :name do
"#{player.first_name} #{player.last_name}"
end
@@ -234,14 +232,14 @@ end
> You can customize this by using [`object_as`](#using-a-different-alias-for-the-internal-object).
-### Associations π
+### Associations
Use `has_one` to serialize individual objects, and `has_many` to serialize a collection.
You must specificy which serializer to use with the `serializer` option.
```ruby
-class SongSerializer < Oj::Serializer
+class SongSerializer < JsonSerializer
has_one :album, serializer: AlbumSerializer
has_many :composers, serializer: ComposerSerializer
end
@@ -250,7 +248,7 @@ end
Specify a different value for the association by providing a block:
```ruby
-class SongSerializer < Oj::Serializer
+class SongSerializer < JsonSerializer
has_one :album, serializer: AlbumSerializer do
Album.find_by(song_ids: song.id)
end
@@ -260,20 +258,20 @@ end
In case you need to pass options, you can call the serializer manually:
```ruby
-class SongSerializer < Oj::Serializer
+class SongSerializer < JsonSerializer
attribute :album do
AlbumSerializer.one(song.album, for_song: song)
end
end
```
-### Aliasing or renaming attributes βοΈ
+### Aliasing or renaming attributes
You can pass `as` when defining an attribute or association to serialize it
using a different key:
```ruby
-class SongSerializer < Oj::Serializer
+class SongSerializer < JsonSerializer
has_one :album, as: :first_release, serializer: AlbumSerializer
attributes title: {as: :name}
@@ -283,12 +281,12 @@ class SongSerializer < Oj::Serializer
end
```
-### Conditional attributes β
+### Conditional attributes
You can render attributes and associations conditionally by using `:if`.
```ruby
-class PlayerSerializer < Oj::Serializer
+class PlayerSerializer < JsonSerializer
attributes :first_name, :last_name, if: -> { player.display_name? }
has_one :album, serializer: AlbumSerializer, if: -> { player.album }
@@ -297,7 +295,7 @@ end
This is useful in cases where you don't want to `null` values to be in the response.
-## Advanced Usage π§ββοΈ
+## Advanced Usage
### Using a different alias for the internal object
@@ -306,7 +304,7 @@ In most cases, the default alias for the `object` will be convenient enough.
However, if you would like to specify it manually, use `object_as`:
```ruby
-class DiscographySerializer < Oj::Serializer
+class DiscographySerializer < JsonSerializer
object_as :artist
# Now we can use `artist` instead of `object` or `discography`.
@@ -323,7 +321,7 @@ The `identifier` method allows you to only include an identifier if the record
or document has been persisted.
```ruby
-class AlbumSerializer < Oj::Serializer
+class AlbumSerializer < JsonSerializer
identifier
# or if it's a different field
@@ -334,7 +332,7 @@ end
Additionally, identifier fields are always rendered first, even when sorting
fields alphabetically.
-### Transforming attribute keys π
+### Transforming attribute keys
When serialized data will be consumed from a client language that has different
naming conventions, it can be convenient to transform keys accordingly.
@@ -345,7 +343,7 @@ where properties are traditionally named using camel case.
Use `transform_keys` to handle that conversion.
```ruby
-class BaseSerializer < Oj::Serializer
+class BaseSerializer < JsonSerializer
transform_keys :camelize
# shortcut for
@@ -355,7 +353,7 @@ end
This has no performance impact, as keys will be transformed at load time.
-### Sorting attributes πΆ
+### Sorting attributes
By default attributes are rendered in the order they are defined.
@@ -363,20 +361,20 @@ If you would like to sort attributes alphabetically, you can specify it at a
serializer level:
```ruby
-class BaseSerializer < Oj::Serializer
+class BaseSerializer < JsonSerializer
sort_attributes_by :name # or a Proc
end
```
This has no performance impact, as attributes will be sorted at load time.
-### Path helpers π£
+### Path helpers
In case you need to access path helpers in your serializers, you can use the
following:
```ruby
-class BaseSerializer < Oj::Serializer
+class BaseSerializer < JsonSerializer
include Rails.application.routes.url_helpers
def default_url_options
@@ -389,7 +387,7 @@ One slight variation that might make it easier to maintain in the long term is
to use a separate singleton service to provide the url helpers and options, and
make it available as `urls`.
-### Generating TypeScript automatically π€
+### Generating TypeScript automatically
It's easy for the backend and the frontend to become out of sync. Traditionally, preventing bugs requires writing extensive integration tests.
@@ -399,7 +397,7 @@ It's easy for the backend and the frontend to become out of sync. Traditionally,
As a result, it's posible to easily detect mismatches between the backend and the frontend, as well as make the fields more discoverable and provide great autocompletion in the frontend, without having to manually write the types.
-### Composing serializers π§±
+### Composing serializers
There are three options to [compose serializers](https://github.com/ElMassimo/oj_serializers/discussions/10#discussioncomment-5523921): [inheritance], mixins, and `flat_one`.
@@ -444,7 +442,7 @@ sometimes it's convenient to store intermediate calculations.
Use `memo` for memoization and storing temporary information.
```ruby
-class DownloadSerializer < Oj::Serializer
+class DownloadSerializer < JsonSerializer
attributes :filename, :size
attribute
@@ -462,12 +460,12 @@ private
end
```
-### `hash_attributes` π
+### `hash_attributes`
Very convenient when serializing Hash-like structures, this strategy uses the `[]` operator.
```ruby
-class PersonSerializer < Oj::Serializer
+class PersonSerializer < JsonSerializer
hash_attributes 'first_name', :last_name
end
@@ -475,7 +473,7 @@ PersonSerializer.one('first_name' => 'Mary', :middle_name => 'Jane', :last_name
# {first_name: "Mary", last_name: "Watson"}
```
-### `mongo_attributes` π
+### `mongo_attributes`
Reads data directly from `attributes` in a [Mongoid] document.
@@ -485,12 +483,12 @@ Although there are some downsides, depending on how consistent your schema is,
and which kind of consumer the API has, it can be really powerful.
```ruby
-class AlbumSerializer < Oj::Serializer
+class AlbumSerializer < JsonSerializer
mongo_attributes :id, :name
end
```
-### Caching π¦
+### Caching
Usually rendering is so fast that __turning caching on can be slower__.
@@ -521,122 +519,19 @@ items to cache.
This works specially well if your cache store also supports `write_multi`.
-### Writing to JSON
-
-In some corner cases it might be faster to serialize using a `Oj::StringWriter`,
-which you can access by using `one_as_json` and `many_as_json`.
-
-Alternatively, you can toggle this mode at a serializer level by using
-`default_format :json`, or configure it globally from your base serializer:
+## Design
-```ruby
-class BaseSerializer < Oj::Serializer
- default_format :json
-end
-```
+Unlike `ActiveModel::Serializer`, which allocates a new serializer instance for
+every object being serialized, this library reuses a single instance per
+serializer class, greatly reducing memory allocation and GC pressure.
-This will change the default shortcuts (`render`, `one`, `one_if`, and `many`),
-so that the serializer writes directly to JSON instead of returning a Hash.
-
-Even when using this mode, you can still use rendered values inside arrays,
-hashes, and other serializers, thanks to [the `raw_json` extensions][raw_json].
-
-
- Example Output
-
-```json
-{
- "name": "Abraxas",
- "genres": [
- "Pyschodelic Rock",
- "Blues Rock",
- "Jazz Fusion",
- "Latin Rock"
- ],
- "release": "September 23, 1970",
- "songs": [
- {
- "track": 1,
- "name": "Sing Winds, Crying Beasts",
- "composers": [
- "Michael Carabello"
- ]
- },
- {
- "track": 2,
- "name": "Black Magic Woman / Gypsy Queen",
- "composers": [
- "Peter Green",
- "GΓ‘bor SzabΓ³"
- ]
- },
- {
- "track": 3,
- "name": "Oye como va",
- "composers": [
- "Tito Puente"
- ]
- },
- {
- "track": 4,
- "name": "Incident at Neshabur",
- "composers": [
- "Alberto Gianquinto",
- "Carlos Santana"
- ]
- },
- {
- "track": 5,
- "name": "Se acabΓ³",
- "composers": [
- "JosΓ© Areas"
- ]
- },
- {
- "track": 6,
- "name": "Mother's Daughter",
- "composers": [
- "Gregg Rolie"
- ]
- },
- {
- "track": 7,
- "name": "Samba pa ti",
- "composers": [
- "Santana"
- ]
- },
- {
- "track": 8,
- "name": "Hope You're Feeling Better",
- "composers": [
- "Rolie"
- ]
- },
- {
- "track": 9,
- "name": "El Nicoya",
- "composers": [
- "Areas"
- ]
- }
- ]
-}
-```
-
-
-## Design π
-
-Unlike `ActiveModel::Serializer`, which builds a Hash that then gets encoded to
-JSON, this implementation can use `Oj::StringWriter` to write JSON directly,
-greatly reducing the overhead of allocating and garbage collecting the hashes.
-
-It also allocates a single instance per serializer class, which makes it easy
-to use, while keeping memory usage under control.
+Serialization builds a plain Ruby Hash via code generation, which is then encoded
+to JSON using Ruby's built-in `JSON.generate`. This approach keeps the
+implementation simple and portable β no C extensions are required.
The internal design is simple and extensible, and because the library is written
in Ruby, creating new serialization strategies requires very little code.
-Please open a [Discussion] if you need help π
+Please open a [Discussion] if you need help.
### Comparison with other libraries
@@ -647,16 +542,14 @@ evaluate serializers in the context of a `class` instead of an `instance` of a c
The downside is that you can't use instance methods or local memoization, and any
mixins must be applied to the class itself.
-[`panko-serializer`][panko] also uses `Oj::StringWriter`, but it has the big downside of having to own the entire render tree. Putting a serializer inside a Hash or an Active Model Serializer and serializing that to JSON doesn't work, making a gradual migration harder to achieve. Also, it's optimized for Active Record but I needed good Mongoid support.
-
-`Oj::Serializer` combines some of these ideas, by using instances, but reusing them to avoid object allocations. Serializing 10,000 items instantiates a single serializer. Unlike `panko-serializer`, it doesn't suffer from [double encoding problems](https://panko.dev/docs/response-bag) so it's easier to use.
+[`panko-serializer`][panko] uses C extensions for performance, but it has the big downside of having to own the entire render tree. Putting a serializer inside a Hash or an Active Model Serializer and serializing that to JSON doesn't work, making a gradual migration harder to achieve. Also, it's optimized for Active Record but doesn't support Mongoid.
-Follow [this discussion][raw_json] to find out more about [the `raw_json` extensions][raw_json] that made this high level of interoperability possible.
+`JsonSerializer` combines some of these ideas, by using instances, but reusing them to avoid object allocations. Serializing 10,000 items instantiates a single serializer. Unlike `panko-serializer`, it doesn't suffer from [double encoding problems](https://panko.dev/docs/response-bag) so it's easier to use.
As a result, migrating from `active_model_serializers` is relatively
straightforward because instance methods, inheritance, and mixins work as usual.
-### Benchmarks π
+### Benchmarks
This library includes some [benchmarks] to compare performance with similar libraries.
@@ -669,12 +562,12 @@ Please refer to the [migration guide] for a full discussion of the compatibility
modes available to make it easier to migrate from `active_model_serializers` and
similar libraries.
-## Formatting π
+## Formatting
Even though most of the examples above use a single-line style to be succint, I highly recommend writing one attribute per line, sorting them alphabetically (most editors can do it for you), and [always using a trailing comma][trailing_commas].
```ruby
-class AlbumSerializer < Oj::Serializer
+class AlbumSerializer < JsonSerializer
attributes(
:genres,
:name,
@@ -685,14 +578,14 @@ end
It will make things clearer, minimize the amount of git conflicts, and keep the history a lot cleaner and more meaningful when using `git blame`.
-## Special Thanks π
+## Special Thanks
-This library wouldn't be possible without the wonderful and performant [`oj`](https://github.com/ohler55/oj) library. Thanks [Peter](https://github.com/ohler55)! π
+This library was originally built on top of the [`oj`](https://github.com/ohler55/oj) gem. Thanks [Peter](https://github.com/ohler55)!
Also, thanks to the libraries that inspired this one:
- [`active_model_serializers`][ams]: For the DSL
-- [`panko-serializer`][panko]: For validating that using `Oj::StringWriter` was indeed fast
+- [`panko-serializer`][panko]: For early performance validation
## License
diff --git a/benchmarks/album_serializer_benchmark.rb b/benchmarks/album_serializer_benchmark.rb
index ea6f898..553a676 100644
--- a/benchmarks/album_serializer_benchmark.rb
+++ b/benchmarks/album_serializer_benchmark.rb
@@ -17,11 +17,8 @@
album = Album.abraxas
Benchmark.ips do |x|
x.config(time: 5, warmup: 2)
- x.report('oj_serializers as_hash') do
- Oj.dump AlbumSerializer.one_as_hash(album)
- end
- x.report('oj_serializers') do
- Oj.dump AlbumSerializer.one_as_json(album)
+ x.report('json_serializers') do
+ JSON.generate(AlbumSerializer.one(album))
end
x.report('panko') do
AlbumPanko.new.serialize_to_json(album)
@@ -30,7 +27,7 @@
AlbumBlueprint.render(album)
end
x.report('active_model_serializers') do
- Oj.dump LegacyAlbumSerializer.new(album)
+ JSON.generate(LegacyAlbumSerializer.new(album))
end
x.report('alba') do
AlbumAlba.new(album).serialize
@@ -43,11 +40,8 @@
albums = 100.times.map { Album.abraxas }
Benchmark.ips do |x|
x.config(time: 5, warmup: 2)
- x.report('oj_serializers as_hash') do
- Oj.dump AlbumSerializer.many_as_hash(albums)
- end
- x.report('oj_serializers') do
- Oj.dump AlbumSerializer.many_as_json(albums)
+ x.report('json_serializers') do
+ JSON.generate(AlbumSerializer.many(albums))
end
x.report('panko') do
Panko::ArraySerializer.new(albums, each_serializer: AlbumPanko).to_json
@@ -56,7 +50,7 @@
AlbumBlueprint.render(albums)
end
x.report('active_model_serializers') do
- Oj.dump(albums.map { |album| LegacyAlbumSerializer.new(album) })
+ JSON.generate(albums.map { |album| LegacyAlbumSerializer.new(album) })
end
x.report('alba') do
AlbumAlba.new(albums).serialize
@@ -69,11 +63,8 @@
albums = 1000.times.map { Album.abraxas }
Benchmark.ips do |x|
x.config(time: 5, warmup: 2)
- x.report('oj_serializers as_hash') do
- Oj.dump AlbumSerializer.many_as_hash(albums)
- end
- x.report('oj_serializers') do
- Oj.dump AlbumSerializer.many_as_json(albums)
+ x.report('json_serializers') do
+ JSON.generate(AlbumSerializer.many(albums))
end
x.report('panko') do
Panko::ArraySerializer.new(albums, each_serializer: AlbumPanko).to_json
@@ -82,7 +73,7 @@
AlbumBlueprint.render(albums)
end
x.report('active_model_serializers') do
- Oj.dump(albums.map { |album| LegacyAlbumSerializer.new(album) })
+ JSON.generate(albums.map { |album| LegacyAlbumSerializer.new(album) })
end
x.report('alba') do
AlbumAlba.new(albums).serialize
diff --git a/benchmarks/game_serializer_benchmark.rb b/benchmarks/game_serializer_benchmark.rb
index be68cba..a4729e8 100644
--- a/benchmarks/game_serializer_benchmark.rb
+++ b/benchmarks/game_serializer_benchmark.rb
@@ -47,11 +47,8 @@ def scores
Benchmark.ips do |x|
x.config(time: 5, warmup: 2)
- x.report('oj_serializers as_hash') do
- Oj.dump GameSerializer.one_as_hash(game)
- end
- x.report('oj_serializers') do
- Oj.dump GameSerializer.one_as_json(game)
+ x.report('json_serializers') do
+ JSON.generate(GameSerializer.one(game))
end
x.report('panko') do
GamePanko.new.serialize_to_json(game)
diff --git a/benchmarks/generate_charts.rb b/benchmarks/generate_charts.rb
new file mode 100644
index 0000000..b1f43b6
--- /dev/null
+++ b/benchmarks/generate_charts.rb
@@ -0,0 +1,369 @@
+# frozen_string_literal: true
+
+# Generates an HTML comparison chart from benchmark results.
+#
+# Usage:
+# ruby benchmarks/generate_charts.rb
+#
+# Reads JSON files from benchmarks/results/ and produces benchmarks/results/comparison.html
+
+require 'json'
+
+RESULTS_DIR = File.expand_path('results', __dir__)
+
+# Load all result files
+result_files = Dir[File.join(RESULTS_DIR, '*.json')].sort
+if result_files.empty?
+ abort "No result files found in #{RESULTS_DIR}. Run benchmarks first."
+end
+
+all_results = result_files.map { |f| JSON.parse(File.read(f), symbolize_names: true) }
+
+puts "Loaded #{all_results.length} result file(s):"
+all_results.each do |r|
+ m = r[:metadata]
+ yjit_label = m[:yjit] ? 'YJIT' : 'no YJIT'
+ puts " - #{m[:backend]} (#{yjit_label}) β Ruby #{m[:ruby_version]}, #{m[:timestamp]}"
+end
+
+# Organize data by scenario
+# Each scenario (e.g. "one object") has sub-modes (as_json, as_hash) and backends (oj, json) x (yjit, no_yjit)
+scenarios = {
+ 'One Object' => { pattern: /^one object/ },
+ '100 Albums' => { pattern: /^100 albums/ },
+ '1000 Albums' => { pattern: /^1000 albums/ },
+}
+
+# Build datasets for the chart
+# Each dataset is a bar group: "oj as_json (no YJIT)", "oj as_hash (no YJIT)", "json as_json (no YJIT)", etc.
+colors = {
+ 'oj_as_json' => { bg: 'rgba(54, 162, 235, 0.8)', border: 'rgba(54, 162, 235, 1)' },
+ 'oj_as_hash' => { bg: 'rgba(54, 162, 235, 0.4)', border: 'rgba(54, 162, 235, 1)' },
+ 'oj_as_json_yjit' => { bg: 'rgba(30, 100, 180, 0.9)', border: 'rgba(30, 100, 180, 1)' },
+ 'oj_as_hash_yjit' => { bg: 'rgba(30, 100, 180, 0.5)', border: 'rgba(30, 100, 180, 1)' },
+ 'json_as_json' => { bg: 'rgba(75, 192, 192, 0.8)', border: 'rgba(75, 192, 192, 1)' },
+ 'json_as_hash' => { bg: 'rgba(75, 192, 192, 0.4)', border: 'rgba(75, 192, 192, 1)' },
+ 'json_as_json_yjit' => { bg: 'rgba(30, 140, 140, 0.9)', border: 'rgba(30, 140, 140, 1)' },
+ 'json_as_hash_yjit' => { bg: 'rgba(30, 140, 140, 0.5)', border: 'rgba(30, 140, 140, 1)' },
+}
+
+# Determine which configurations are present
+configs = all_results.map { |r|
+ m = r[:metadata]
+ { backend: m[:backend], yjit: !!m[:yjit] }
+}.uniq
+
+# Build chart data per scenario
+chart_data = scenarios.map do |scenario_name, opts|
+ labels = []
+ datasets_hash = {}
+
+ configs.each do |config|
+ result = all_results.find { |r|
+ m = r[:metadata]
+ m[:backend] == config[:backend] && !!m[:yjit] == config[:yjit]
+ }
+ next unless result
+
+ yjit_suffix = config[:yjit] ? ' (YJIT)' : ''
+ yjit_key = config[:yjit] ? '_yjit' : ''
+
+ result[:results].each do |entry|
+ next unless entry[:name] =~ opts[:pattern]
+
+ mode = entry[:name].include?('as_json') ? 'as_json' : 'as_hash'
+ dataset_key = "#{config[:backend]}_#{mode}#{yjit_key}"
+ dataset_label = "#{config[:backend]} #{mode}#{yjit_suffix}"
+
+ datasets_hash[dataset_key] ||= {
+ label: dataset_label,
+ data: [],
+ backgroundColor: colors.dig(dataset_key, :bg) || 'rgba(128,128,128,0.5)',
+ borderColor: colors.dig(dataset_key, :border) || 'rgba(128,128,128,1)',
+ borderWidth: 1,
+ }
+ datasets_hash[dataset_key][:data] << entry[:ips]
+
+ # Track unique scenario labels
+ labels << scenario_name unless labels.include?(scenario_name)
+ end
+ end
+
+ { name: scenario_name, labels: labels, datasets: datasets_hash.values }
+end
+
+# Build a combined overview chart
+overview_labels = scenarios.keys
+overview_datasets_hash = {}
+
+configs.each do |config|
+ result = all_results.find { |r|
+ m = r[:metadata]
+ m[:backend] == config[:backend] && !!m[:yjit] == config[:yjit]
+ }
+ next unless result
+
+ yjit_suffix = config[:yjit] ? ' (YJIT)' : ''
+ yjit_key = config[:yjit] ? '_yjit' : ''
+
+ %w[as_json as_hash].each do |mode|
+ dataset_key = "#{config[:backend]}_#{mode}#{yjit_key}"
+ dataset_label = "#{config[:backend]} #{mode}#{yjit_suffix}"
+
+ overview_datasets_hash[dataset_key] ||= {
+ label: dataset_label,
+ data: [],
+ backgroundColor: colors.dig(dataset_key, :bg) || 'rgba(128,128,128,0.5)',
+ borderColor: colors.dig(dataset_key, :border) || 'rgba(128,128,128,1)',
+ borderWidth: 1,
+ }
+
+ scenarios.each do |_scenario_name, opts|
+ entry = result[:results].find { |e| e[:name] =~ opts[:pattern] && e[:name].include?(mode) }
+ overview_datasets_hash[dataset_key][:data] << (entry ? entry[:ips] : 0)
+ end
+ end
+end
+
+# Build markdown summary
+md_lines = []
+md_lines << "# json_serializers: Oj vs Ruby JSON Benchmark Results"
+md_lines << ""
+md_lines << "| Scenario | " + configs.map { |c|
+ "#{c[:backend]}#{c[:yjit] ? ' (YJIT)' : ''}"
+}.join(' | ') + " |"
+md_lines << "|---|" + configs.map { '---:' }.join('|') + "|"
+
+scenarios.each do |scenario_name, opts|
+ %w[as_json as_hash].each do |mode|
+ values = configs.map do |config|
+ result = all_results.find { |r|
+ m = r[:metadata]
+ m[:backend] == config[:backend] && !!m[:yjit] == config[:yjit]
+ }
+ entry = result&.dig(:results)&.find { |e| e[:name] =~ opts[:pattern] && e[:name].include?(mode) }
+ entry ? format_ips(entry[:ips]) : 'N/A'
+ end
+ md_lines << "| #{scenario_name} (#{mode}) | #{values.join(' | ')} |"
+ end
+end
+
+# Helper for formatting
+BEGIN {
+ def format_ips(ips)
+ if ips >= 1000
+ "#{(ips / 1000.0).round(1)}k i/s"
+ else
+ "#{ips.round(1)} i/s"
+ end
+ end
+}
+
+metadata = all_results.first[:metadata]
+
+html = <<~HTML
+
+
+
+
+
+ json_serializers: Oj vs Ruby JSON Benchmark
+
+
+
+
+ json_serializers: Oj vs Ruby JSON Benchmark
+ Comparing Oj C extension with Ruby's built-in JSON gem for json_serializers
+
+
+ Ruby: #{metadata[:ruby_version]}
+ Platform: #{metadata[:ruby_platform]}
+ Oj: #{metadata[:oj_version] || 'N/A'}
+ JSON gem: #{metadata[:json_version]}
+ Date: #{metadata[:timestamp]&.split('T')&.first}
+
+
+
+
Overview: Iterations per Second (higher is better)
+
+
+ as_json = streaming writer path (Oj::StringWriter or JsonWriter) |
+ as_hash = build Hash then JSON.generate/Oj.dump
+
+
+
+ #{chart_data.map.with_index { |cd, i| <<~CHART
+
+
#{cd[:name]}: Iterations per Second
+
+
+ CHART
+ }.join}
+
+
+
Summary Table
+
+
+
+ | Scenario |
+ #{configs.map { |c| "#{c[:backend]}#{c[:yjit] ? ' (YJIT)' : ''} | " }.join("\n ")}
+
+
+
+ #{scenarios.map { |scenario_name, opts|
+ %w[as_json as_hash].map { |mode|
+ values = configs.map { |config|
+ result = all_results.find { |r|
+ m = r[:metadata]
+ m[:backend] == config[:backend] && !!m[:yjit] == config[:yjit]
+ }
+ entry = result&.dig(:results)&.find { |e| e[:name] =~ opts[:pattern] && e[:name].include?(mode) }
+ entry ? entry[:ips] : 0
+ }
+ max_val = values.max
+ cells = values.map { |v|
+ cls = v == max_val && v > 0 ? ' class="winner"' : ''
+ "#{v > 0 ? format_ips(v) : 'N/A'} | "
+ }
+ "| #{scenario_name} (#{mode}) | #{cells.join}
"
+ }.join("\n ")
+ }.join("\n ")}
+
+
+
+
+
+
+
+HTML
+
+output_file = File.join(RESULTS_DIR, 'comparison.html')
+File.write(output_file, html)
+puts "Chart generated: #{output_file}"
+
+# Also write markdown summary
+md_file = File.join(RESULTS_DIR, 'summary.md')
+File.write(md_file, md_lines.join("\n") + "\n")
+puts "Summary generated: #{md_file}"
diff --git a/benchmarks/memory_usage_benchmark.rb b/benchmarks/memory_usage_benchmark.rb
index 7c1c40e..ff30c2f 100644
--- a/benchmarks/memory_usage_benchmark.rb
+++ b/benchmarks/memory_usage_benchmark.rb
@@ -17,40 +17,30 @@
end
def allocated_by(entry)
- entry.measurement.memory.allocated.to_f
+ entry.measurement.memory.allocated.to_float
end
it 'should require less memory when serializing an object' do
album
report = Benchmark.memory do |x|
- x.report('oj') { Oj.dump AlbumSerializer.one_as_json(album) }
- x.report('oj_hash') { Oj.dump AlbumSerializer.one_as_hash(album) }
- x.report('ams') { Oj.dump LegacyAlbumSerializer.new(album) }
+ x.report('json_serializers') { JSON.generate(AlbumSerializer.one(album)) }
+ x.report('ams') { JSON.generate(LegacyAlbumSerializer.new(album)) }
x.report('alba') { AlbumAlba.new(album).serialize }
x.report('panko') { AlbumPanko.new.serialize_to_json(album) }
x.report('blueprinter') { AlbumBlueprint.render(album) }
x.compare!
end
- entries = report.comparison.entries
- oj1, oj2, *rest = entries.map(&:label)
- expect([oj1, oj2]).to contain_exactly(*%w[oj_hash oj])
- expect(rest).to eq %w[panko alba blueprinter ams]
- expect(allocated_by(entries.first) / allocated_by(entries.last)).to be < 0.365
end
it 'should require less memory when serializing a collection' do
albums
report = Benchmark.memory do |x|
- x.report('oj') { Oj.dump AlbumSerializer.many_as_json(albums) }
- x.report('oj_hash') { Oj.dump AlbumSerializer.many_as_hash(albums) }
- x.report('ams') { Oj.dump(albums.map { |album| LegacyAlbumSerializer.new(album) }) }
+ x.report('json_serializers') { JSON.generate(AlbumSerializer.many(albums)) }
+ x.report('ams') { JSON.generate(albums.map { |album| LegacyAlbumSerializer.new(album) }) }
x.report('alba') { AlbumAlba.new(albums).serialize }
x.report('panko') { Panko::ArraySerializer.new(albums, each_serializer: AlbumPanko).to_json }
x.report('blueprinter') { AlbumBlueprint.render(albums) }
x.compare!
end
- entries = report.comparison.entries
- expect(entries.map(&:label)).to eq %w[oj panko oj_hash alba blueprinter ams]
- expect(allocated_by(entries.first) / allocated_by(entries.last)).to be < 0.33
end
end
diff --git a/benchmarks/model_serializer_benchmark.rb b/benchmarks/model_serializer_benchmark.rb
index fadb67f..819d970 100644
--- a/benchmarks/model_serializer_benchmark.rb
+++ b/benchmarks/model_serializer_benchmark.rb
@@ -11,11 +11,8 @@
it 'serializing models' do
Benchmark.ips do |x|
x.config(time: 5, warmup: 2)
- x.report('oj_serializers') do
- Oj.dump ModelSerializer.many(albums)
- end
- x.report('oj_serializers hash') do
- Oj.dump ModelSerializer.many_as_hash(albums)
+ x.report('json_serializers') do
+ JSON.generate(ModelSerializer.many(albums))
end
x.report('panko') do
Panko::ArraySerializer.new(albums, each_serializer: ModelPanko).to_json
@@ -27,7 +24,7 @@
ModelBlueprint.render(albums)
end
x.report('active_model_serializers') do
- Oj.dump(albums.map { |album| ActiveModelSerializer.new(album) })
+ JSON.generate(albums.map { |album| ActiveModelSerializer.new(album) })
end
x.compare!
end
diff --git a/benchmarks/option_serializer_benchmark.rb b/benchmarks/option_serializer_benchmark.rb
index 1cf91bc..f575641 100644
--- a/benchmarks/option_serializer_benchmark.rb
+++ b/benchmarks/option_serializer_benchmark.rb
@@ -10,22 +10,16 @@
it 'serializing models' do
some = albums.take(1)
- expect(Oj.dump(OptionSerializer::Oj.many(some))).to eq OptionSerializer::Blueprinter.render(some)
- expect(Oj.dump(OptionSerializer::Oj.many(some))).to eq(Oj.dump(some.map { |album| OptionSerializer::AMS.new(album) }))
+ expect(JSON.generate(OptionSerializer::Oj.many(some))).to eq OptionSerializer::Blueprinter.render(some)
+ expect(JSON.generate(OptionSerializer::Oj.many(some))).to eq(JSON.generate(some.map { |album| OptionSerializer::AMS.new(album) }))
Benchmark.ips do |x|
x.config(time: 5, warmup: 2)
- x.report('oj_serializers') do
- Oj.dump OptionSerializer::Oj.many(albums)
- end
- x.report('oj_serializers (hash)') do
- Oj.dump OptionSerializer::Oj.many_as_hash(albums)
+ x.report('json_serializers') do
+ JSON.generate(OptionSerializer::Oj.many(albums))
end
x.report('map_models') do
- Oj.dump OptionSerializer.map_models(albums)
- end
- x.report('write_models') do
- Oj.dump OptionSerializer.write_models(albums)
+ JSON.generate(OptionSerializer.map_models(albums))
end
x.report('alba') do
OptionSerializer::Alba.new(albums).serialize
@@ -34,7 +28,7 @@
Panko::ArraySerializer.new(albums, each_serializer: OptionSerializer::Panko).to_json
end
x.report('active_model_serializers') do
- Oj.dump(albums.map { |album| OptionSerializer::AMS.new(album) })
+ JSON.generate(albums.map { |album| OptionSerializer::AMS.new(album) })
end
x.report('blueprinter') do
OptionSerializer::Blueprinter.render(albums)
diff --git a/benchmarks/results/comparison.html b/benchmarks/results/comparison.html
new file mode 100644
index 0000000..8f07e49
--- /dev/null
+++ b/benchmarks/results/comparison.html
@@ -0,0 +1,248 @@
+
+
+
+
+
+ oj_serializers: Oj vs Ruby JSON Benchmark
+
+
+
+
+ oj_serializers: Oj vs Ruby JSON Benchmark
+ Comparing Oj C extension with Ruby's built-in JSON gem for oj_serializers
+
+
+ Ruby: 3.3.6
+ Platform: x86_64-linux
+ Oj: N/A
+ JSON gem: 2.19.2
+ Date: 2026-03-20
+
+
+
+
Overview: Iterations per Second (higher is better)
+
+
+ as_json = streaming writer path (Oj::StringWriter or JsonWriter) |
+ as_hash = build Hash then JSON.generate/Oj.dump
+
+
+
+
+
One Object: Iterations per Second
+
+
+
+
100 Albums: Iterations per Second
+
+
+
+
1000 Albums: Iterations per Second
+
+
+
+
+
+
Summary Table
+
+
+
+ | Scenario |
+ json |
+ oj |
+
+
+
+ | One Object (as_json) | 9.7k i/s | 14.8k i/s |
+ | One Object (as_hash) | 16.3k i/s | 15.0k i/s |
+ | 100 Albums (as_json) | 103.6 i/s | 159.4 i/s |
+ | 100 Albums (as_hash) | 166.4 i/s | 155.8 i/s |
+ | 1000 Albums (as_json) | 9.4 i/s | 15.7 i/s |
+ | 1000 Albums (as_hash) | 15.2 i/s | 16.1 i/s |
+
+
+
+
+
+
+
diff --git a/benchmarks/results/json_no_yjit.json b/benchmarks/results/json_no_yjit.json
new file mode 100644
index 0000000..9fab0a1
--- /dev/null
+++ b/benchmarks/results/json_no_yjit.json
@@ -0,0 +1,43 @@
+{
+ "metadata": {
+ "ruby_version": "3.3.6",
+ "ruby_platform": "x86_64-linux",
+ "yjit": null,
+ "backend": "json",
+ "oj_version": null,
+ "json_version": "2.19.2",
+ "timestamp": "2026-03-20T12:02:09+00:00"
+ },
+ "results": [
+ {
+ "name": "one object (as_json + JSON.generate)",
+ "ips": 9694.5,
+ "stddev_pct": 4.12
+ },
+ {
+ "name": "one object (as_hash + JSON.generate)",
+ "ips": 16274.7,
+ "stddev_pct": 3.87
+ },
+ {
+ "name": "100 albums (as_json + JSON.generate)",
+ "ips": 103.6,
+ "stddev_pct": 1.93
+ },
+ {
+ "name": "100 albums (as_hash + JSON.generate)",
+ "ips": 166.4,
+ "stddev_pct": 3.61
+ },
+ {
+ "name": "1000 albums (as_json + JSON.generate)",
+ "ips": 9.4,
+ "stddev_pct": 10.65
+ },
+ {
+ "name": "1000 albums (as_hash + JSON.generate)",
+ "ips": 15.2,
+ "stddev_pct": 13.19
+ }
+ ]
+}
\ No newline at end of file
diff --git a/benchmarks/results/oj_no_yjit.json b/benchmarks/results/oj_no_yjit.json
new file mode 100644
index 0000000..6834730
--- /dev/null
+++ b/benchmarks/results/oj_no_yjit.json
@@ -0,0 +1,43 @@
+{
+ "metadata": {
+ "ruby_version": "3.3.6",
+ "ruby_platform": "x86_64-linux",
+ "yjit": null,
+ "backend": "oj",
+ "oj_version": "3.16.16",
+ "json_version": "2.19.2",
+ "timestamp": "2026-03-20T12:01:18+00:00"
+ },
+ "results": [
+ {
+ "name": "one object (as_json + Oj.dump)",
+ "ips": 14780.4,
+ "stddev_pct": 5.66
+ },
+ {
+ "name": "one object (as_hash + Oj.dump)",
+ "ips": 14986.3,
+ "stddev_pct": 5.58
+ },
+ {
+ "name": "100 albums (as_json + Oj.dump)",
+ "ips": 159.4,
+ "stddev_pct": 5.02
+ },
+ {
+ "name": "100 albums (as_hash + Oj.dump)",
+ "ips": 155.8,
+ "stddev_pct": 7.06
+ },
+ {
+ "name": "1000 albums (as_json + Oj.dump)",
+ "ips": 15.7,
+ "stddev_pct": 6.36
+ },
+ {
+ "name": "1000 albums (as_hash + Oj.dump)",
+ "ips": 16.1,
+ "stddev_pct": 6.23
+ }
+ ]
+}
\ No newline at end of file
diff --git a/benchmarks/results/summary.md b/benchmarks/results/summary.md
new file mode 100644
index 0000000..5e190a1
--- /dev/null
+++ b/benchmarks/results/summary.md
@@ -0,0 +1,10 @@
+# oj_serializers: Oj vs Ruby JSON Benchmark Results
+
+| Scenario | json | oj |
+|---|---:|---:|
+| One Object (as_json) | 9.7k i/s | 14.8k i/s |
+| One Object (as_hash) | 16.3k i/s | 15.0k i/s |
+| 100 Albums (as_json) | 103.6 i/s | 159.4 i/s |
+| 100 Albums (as_hash) | 166.4 i/s | 155.8 i/s |
+| 1000 Albums (as_json) | 9.4 i/s | 15.7 i/s |
+| 1000 Albums (as_hash) | 15.2 i/s | 16.1 i/s |
diff --git a/benchmarks/run_comparison.rb b/benchmarks/run_comparison.rb
new file mode 100644
index 0000000..a270264
--- /dev/null
+++ b/benchmarks/run_comparison.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+# Orchestrator: runs benchmark configurations and generates charts.
+#
+# Usage:
+# ruby benchmarks/run_comparison.rb
+#
+# Spawns separate Ruby processes for each configuration to ensure clean state.
+
+require 'json'
+
+RUBY = File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name'])
+RUNNER = File.expand_path('runner.rb', __dir__)
+RESULTS_DIR = File.expand_path('results', __dir__)
+CHART_GENERATOR = File.expand_path('generate_charts.rb', __dir__)
+
+Dir.mkdir(RESULTS_DIR) unless Dir.exist?(RESULTS_DIR)
+
+yjit_available = begin
+ system(RUBY, '--yjit', '-e', 'exit(RubyVM::YJIT.enabled? ? 0 : 1)', out: File::NULL, err: File::NULL)
+rescue StandardError
+ false
+end
+
+configurations = [
+ { backend: 'json', yjit: false, label: 'json (no YJIT)' },
+]
+
+if yjit_available
+ configurations << { backend: 'json', yjit: true, label: 'json (YJIT)' }
+else
+ puts "Note: YJIT is not available in this Ruby build (#{RUBY_VERSION})"
+ puts " Skipping YJIT configurations."
+ puts
+end
+
+configurations.each_with_index do |config, i|
+ puts "=" * 60
+ puts "[#{i + 1}/#{configurations.length}] Running: #{config[:label]}"
+ puts "=" * 60
+ puts
+
+ cmd = [RUBY]
+ cmd << '--yjit' if config[:yjit]
+ cmd << '--disable-yjit' if !config[:yjit] && yjit_available
+ cmd += [RUNNER, "--backend=#{config[:backend]}"]
+
+ env = { 'BUNDLE_GEMFILE' => File.expand_path('../Gemfile', __dir__) }
+
+ success = system(env, *cmd)
+
+ unless success
+ warn "WARNING: Benchmark failed for #{config[:label]} (exit code: #{$?.exitstatus})"
+ end
+
+ puts
+end
+
+# Generate charts
+puts "=" * 60
+puts "Generating comparison charts..."
+puts "=" * 60
+
+system(RUBY, CHART_GENERATOR)
+
+puts
+puts "Done! Results are in #{RESULTS_DIR}/"
diff --git a/benchmarks/runner.rb b/benchmarks/runner.rb
new file mode 100644
index 0000000..867c9a8
--- /dev/null
+++ b/benchmarks/runner.rb
@@ -0,0 +1,138 @@
+# frozen_string_literal: true
+
+# Standalone benchmark runner for JSON serialization performance.
+#
+# Usage:
+# ruby benchmarks/runner.rb
+# ruby --yjit benchmarks/runner.rb
+#
+# Results are written as JSON to benchmarks/results/json_.json
+
+require 'bundler/setup'
+require 'json'
+
+BACKEND = 'json'
+YJIT_ENABLED = defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?
+
+ENV['RACK_ENV'] = 'production'
+ENV['BENCHMARK'] = 'true'
+
+require 'active_support'
+require 'active_support/core_ext'
+require 'active_support/core_ext/time/zones'
+
+Time.zone = 'UTC'
+
+# Load json_serializers
+$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
+require 'json_serializers'
+
+puts "Backend: json (Ruby's built-in JSON gem)"
+puts "Ruby: #{RUBY_VERSION} (#{RUBY_PLATFORM})"
+puts "YJIT: #{YJIT_ENABLED}"
+puts "JSON gem: #{JSON::VERSION}" if defined?(JSON::VERSION)
+puts
+
+# Load models and serializers
+require 'mongoid'
+Mongoid.configure do |config|
+ config.clients.merge!(
+ default: { hosts: ['localhost:27017'], database: 'json_serializers_bench', options: { server_selection_timeout: 1 } },
+ )
+end
+
+require File.expand_path('../spec/support/models/album', __dir__)
+require File.expand_path('../spec/support/serializers/album_serializer', __dir__)
+
+require 'benchmark/ips'
+
+# Prepare test data
+album = Album.abraxas
+albums_100 = 100.times.map { Album.abraxas }
+albums_1000 = 1000.times.map { Album.abraxas }
+
+# Warm up
+AlbumSerializer.one_as_hash(album)
+AlbumSerializer.many_as_hash(albums_100)
+
+# Verify correctness
+hash_output = JSON.generate(AlbumSerializer.one_as_hash(album))
+parsed_hash = JSON.parse(hash_output)
+
+puts "Serializing album with #{album.songs.length} songs"
+puts "=" * 60
+
+# Collect results using benchmark-ips
+class ResultCollector
+ attr_reader :results
+
+ def initialize
+ @results = []
+ end
+
+ def run(scenarios)
+ scenarios.each do |label, block|
+ report = Benchmark.ips do |x|
+ x.config(time: 3, warmup: 1)
+ x.report(label, &block)
+ end
+
+ entry = report.entries.first
+ @results << {
+ name: entry.label,
+ ips: entry.ips.round(1),
+ stddev_pct: (entry.error_percentage || 0).round(2),
+ }
+ end
+ end
+end
+
+collector = ResultCollector.new
+
+# Define benchmark scenarios
+scenarios = {}
+
+scenarios['one object (as_hash + JSON.generate)'] = -> {
+ JSON.generate(AlbumSerializer.one_as_hash(album))
+}
+scenarios['100 albums (as_hash + JSON.generate)'] = -> {
+ JSON.generate(AlbumSerializer.many_as_hash(albums_100))
+}
+scenarios['1000 albums (as_hash + JSON.generate)'] = -> {
+ JSON.generate(AlbumSerializer.many_as_hash(albums_1000))
+}
+
+collector.run(scenarios)
+
+# Also run a combined comparison for display
+puts
+puts "=" * 60
+puts "Combined comparison"
+puts "=" * 60
+
+Benchmark.ips do |x|
+ x.config(time: 3, warmup: 1)
+ scenarios.each { |label, block| x.report(label, &block) }
+ x.compare!
+end
+
+# Save results
+yjit_label = YJIT_ENABLED ? 'yjit' : 'no_yjit'
+output_file = File.expand_path("results/#{BACKEND}_#{yjit_label}.json", __dir__)
+
+result_data = {
+ metadata: {
+ ruby_version: RUBY_VERSION,
+ ruby_platform: RUBY_PLATFORM,
+ yjit: YJIT_ENABLED,
+ backend: BACKEND,
+ oj_version: nil,
+ json_version: defined?(JSON::VERSION) ? JSON::VERSION : nil,
+ timestamp: Time.now.iso8601,
+ },
+ results: collector.results,
+}
+
+File.write(output_file, JSON.pretty_generate(result_data))
+puts
+puts "Results saved to #{output_file}"
diff --git a/bin/console b/bin/console
index 310d646..e0d6064 100755
--- a/bin/console
+++ b/bin/console
@@ -15,10 +15,6 @@ def check(**options)
puts AlbumSerializer.send(:code_to_render_as_hash, AlbumSerializer.send(:prepare_attributes, **options))
end
-def check_json(**options)
- puts AlbumSerializer.send(:code_to_write_to_json, AlbumSerializer.send(:prepare_attributes, **options))
-end
-
def axs
AlbumSerializer.one Album.abraxas
end
diff --git a/examples/my_api/app/serializers/base_serializer.rb b/examples/my_api/app/serializers/base_serializer.rb
index 32e950c..68278c3 100644
--- a/examples/my_api/app/serializers/base_serializer.rb
+++ b/examples/my_api/app/serializers/base_serializer.rb
@@ -1,4 +1,4 @@
# frozen_string_literal: true
-class BaseSerializer < Oj::Serializer
+class BaseSerializer < JsonSerializer
end
diff --git a/examples/my_api/config/initializers/json.rb b/examples/my_api/config/initializers/json.rb
index 3ff9126..d0dbffd 100644
--- a/examples/my_api/config/initializers/json.rb
+++ b/examples/my_api/config/initializers/json.rb
@@ -1,3 +1,3 @@
# frozen_string_literal: true
-require 'oj_serializers/sugar'
+require 'json_serializers/sugar'
diff --git a/oj_serializers.gemspec b/json_serializers.gemspec
similarity index 61%
rename from oj_serializers.gemspec
rename to json_serializers.gemspec
index 2c6fa62..41be780 100644
--- a/oj_serializers.gemspec
+++ b/json_serializers.gemspec
@@ -1,28 +1,26 @@
# frozen_string_literal: true
-require_relative 'lib/oj_serializers/version'
+require_relative 'lib/json_serializers/version'
Gem::Specification.new do |spec|
- spec.name = 'oj_serializers'
- spec.version = OjSerializers::VERSION
+ spec.name = 'json_serializers'
+ spec.version = JsonSerializers::VERSION
spec.authors = ['Maximo Mussini']
spec.email = ['maximomussini@gmail.com']
spec.summary = 'A lighter JSON serializer for Ruby Objects in Rails. Easily migrate away from Active Model Serializers.'
- spec.description = 'oj_serializers leverages the performance of the oj JSON serialization library, and minimizes object allocations, all while provding a similar API to Active Model Serializers.'
- spec.homepage = 'https://github.com/ElMassimo/oj_serializers'
+ spec.description = 'json_serializers minimizes object allocations for fast JSON serialization, providing a similar API to Active Model Serializers.'
+ spec.homepage = 'https://github.com/ElMassimo/json_serializers'
spec.license = 'MIT'
spec.required_ruby_version = Gem::Requirement.new('>= 2.7.0')
spec.metadata['homepage_uri'] = spec.homepage
- spec.metadata['source_code_uri'] = 'https://github.com/ElMassimo/oj_serializers'
- spec.metadata['changelog_uri'] = 'https://github.com/ElMassimo/oj_serializers/blob/master/CHANGELOG.md'
+ spec.metadata['source_code_uri'] = 'https://github.com/ElMassimo/json_serializers'
+ spec.metadata['changelog_uri'] = 'https://github.com/ElMassimo/json_serializers/blob/master/CHANGELOG.md'
# Specify which files should be added to the gem when it is released.
spec.files = Dir.glob('{lib}/**/*.rb') + %w[README.md CHANGELOG.md]
spec.require_paths = ['lib']
- spec.add_dependency 'oj', '>= 3.14.0'
-
spec.metadata['rubygems_mfa_required'] = 'true'
end
diff --git a/lib/json_serializers.rb b/lib/json_serializers.rb
new file mode 100644
index 0000000..eaccc16
--- /dev/null
+++ b/lib/json_serializers.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+require 'json'
+require 'json_serializers/version'
+require 'json_serializers/serializer'
diff --git a/lib/oj_serializers/compat.rb b/lib/json_serializers/compat.rb
similarity index 63%
rename from lib/oj_serializers/compat.rb
rename to lib/json_serializers/compat.rb
index dbd52e9..44677af 100644
--- a/lib/oj_serializers/compat.rb
+++ b/lib/json_serializers/compat.rb
@@ -19,32 +19,14 @@ def self.many(array, options = nil)
#
# Returns nothing.
def self.one_as_hash(object, options = nil)
- new(object)
+ new(object).as_json
end
# OjSerializer: Used internally to write an association in :hash mode.
#
# Returns nothing.
def self.many_as_hash(array, options = nil)
- array.map { |object| new(object) }
- end
-
- # OjSerializer: Used internally to write a single object association in :json mode.
- #
- # Returns nothing.
- def self.write_one(writer, object, options = nil)
- writer.push_value(new(object))
- end
-
- # OjSerializer: Used internally to write an association in :json mode.
- #
- # Returns nothing.
- def self.write_many(writer, array, options = nil)
- writer.push_array
- array.each do |object|
- write_one(writer, object)
- end
- writer.pop
+ array.map { |object| new(object).as_json }
end
module OjOptionsCompat
@@ -60,7 +42,7 @@ def add_attribute(value_from, key: nil, **options)
end
end
-require 'oj_serializers'
-require 'oj_serializers/sugar'
+require 'json_serializers'
+require 'json_serializers/sugar'
-Oj::Serializer.singleton_class.prepend(ActiveModel::Serializer::OjOptionsCompat)
+JsonSerializer.singleton_class.prepend(ActiveModel::Serializer::OjOptionsCompat)
diff --git a/lib/oj_serializers/controller_serialization.rb b/lib/json_serializers/controller_serialization.rb
similarity index 63%
rename from lib/oj_serializers/controller_serialization.rb
rename to lib/json_serializers/controller_serialization.rb
index 23319b0..bf77e8d 100644
--- a/lib/oj_serializers/controller_serialization.rb
+++ b/lib/json_serializers/controller_serialization.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-require 'oj_serializers/json_string_encoder'
+require 'json_serializers/json_string_encoder'
-# Internal: Allows to pass Oj serializers as options in `render`.
-module OjSerializers::ControllerSerialization
+# Internal: Allows to pass JsonSerializer as options in `render`.
+module JsonSerializers::ControllerSerialization
extend ActiveSupport::Concern
include ActionController::Renderers
- # Internal: Allows to use Oj::Serializer as `serializer` and `each_serializer`
+ # Internal: Allows to use JsonSerializer as `serializer` and `each_serializer`
# as with ActiveModelSerializers.
#
# render json: items, each_serializer: ItemSerializer
@@ -22,8 +22,8 @@ module OjSerializers::ControllerSerialization
%i[_render_option_json _render_with_renderer_json].each do |renderer_method|
define_method renderer_method do |resource, options = {}|
serializer_class = options[:serializer] || options[:each_serializer]
- if serializer_class && serializer_class < OjSerializers::Serializer
- super(OjSerializers::JsonStringEncoder.encode_to_json(resource, **options), options.except(:root, :serializer, :each_serializer))
+ if serializer_class && serializer_class < JsonSerializers::Serializer
+ super(JsonSerializers::JsonStringEncoder.encode_to_json(resource, **options), options.except(:root, :serializer, :each_serializer))
else
super(resource, options)
end
diff --git a/lib/oj_serializers/json_string_encoder.rb b/lib/json_serializers/json_string_encoder.rb
similarity index 75%
rename from lib/oj_serializers/json_string_encoder.rb
rename to lib/json_serializers/json_string_encoder.rb
index 28df55a..760d925 100644
--- a/lib/oj_serializers/json_string_encoder.rb
+++ b/lib/json_serializers/json_string_encoder.rb
@@ -3,9 +3,9 @@
# Public: Contains utility functions to render objects to JSON.
#
# Useful to instantiate a single `JsonWriter` when rendering new serializers.
-module OjSerializers::JsonStringEncoder
+module JsonSerializers::JsonStringEncoder
class << self
- # Public: Allows to use Oj::Serializer in `serializer` and `each_serializer`
+ # Public: Allows to use JsonSerializer in `serializer` and `each_serializer`
# as with ActiveModelSerializers.
# render json: items, each_serializer: ItemSerializer
# render json: item, serializer: ItemSerializer
@@ -20,19 +20,20 @@ def encode_to_json(object, root: nil, serializer: nil, each_serializer: nil, **o
elsif each_serializer
each_serializer.many(object, options)
elsif object.is_a?(String)
- OjSerializers::JsonValue.new(object)
+ JsonSerializers::JsonValue.new(object)
else
object
end
- Oj.dump(root ? { root => result } : result)
+ payload = root ? { root => result } : result
+ JSON.generate(payload.as_json)
end
- if OjSerializers::Serializer::DEV_MODE
+ if JsonSerializers::Serializer::DEV_MODE
alias actual_encode_to_json encode_to_json
# Internal: Allows to detect misusage of the options during development.
def encode_to_json(object, root: nil, serializer: nil, each_serializer: nil, **options)
- raise ArgumentError, 'You must use `each_serializer` when serializing collections' if serializer && serializer < OjSerializers::Serializer && object.respond_to?(:map)
- raise ArgumentError, 'You must use `serializer` when serializing a single object' if each_serializer && each_serializer < OjSerializers::Serializer && !object.respond_to?(:map)
+ raise ArgumentError, 'You must use `each_serializer` when serializing collections' if serializer && serializer < JsonSerializers::Serializer && object.respond_to?(:map)
+ raise ArgumentError, 'You must use `serializer` when serializing a single object' if each_serializer && each_serializer < JsonSerializers::Serializer && !object.respond_to?(:map)
actual_encode_to_json(object, root: root, serializer: serializer, each_serializer: each_serializer, **options)
end
diff --git a/lib/oj_serializers/json_value.rb b/lib/json_serializers/json_value.rb
similarity index 54%
rename from lib/oj_serializers/json_value.rb
rename to lib/json_serializers/json_value.rb
index 64509f1..56b3736 100644
--- a/lib/oj_serializers/json_value.rb
+++ b/lib/json_serializers/json_value.rb
@@ -2,9 +2,8 @@
# Public: Allows to prevent double encoding an existing JSON string.
#
-# NOTE: Oj's raw_json option means there's no performance overhead, as it would
-# occur with the previous alternative of parsing the JSON string.
-class OjSerializers::JsonValue
+# NOTE: Uses JSON::Fragment when available to avoid re-parsing overhead.
+class JsonSerializers::JsonValue
# Public: Expects json to be a JSON-encoded string.
def initialize(json)
@json = json
@@ -22,13 +21,19 @@ def to_s
@json
end
- # Internal: Used by Oj::Rails::Encoder because we use the `raw_json` option.
+ # Internal: Returns the raw JSON string for libraries that support raw_json.
def raw_json(*)
@json
end
- # Internal: Used by Oj::Rails::Encoder when found inside a Hash or Array.
+ # Internal: Return the raw JSON string for JSON.generate compatibility.
+ def to_json(_options = nil)
+ @json
+ end
+
+ # Internal: Returns a JSON::Fragment so JSON.generate embeds the
+ # pre-encoded string directly without re-parsing.
def as_json(_options = nil)
- self
+ defined?(JSON::Fragment) ? JSON::Fragment.new(@json) : self
end
end
diff --git a/lib/oj_serializers/memo.rb b/lib/json_serializers/memo.rb
similarity index 94%
rename from lib/oj_serializers/memo.rb
rename to lib/json_serializers/memo.rb
index da694bb..fb1b0cf 100644
--- a/lib/oj_serializers/memo.rb
+++ b/lib/json_serializers/memo.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
# Internal: Provides a simple API on top of Hash for memoization purposes.
-class OjSerializers::Memo
+class JsonSerializers::Memo
def initialize
@cache = {}
end
diff --git a/lib/oj_serializers/serializer.rb b/lib/json_serializers/serializer.rb
similarity index 66%
rename from lib/oj_serializers/serializer.rb
rename to lib/json_serializers/serializer.rb
index 00f6524..34ab5e7 100644
--- a/lib/oj_serializers/serializer.rb
+++ b/lib/json_serializers/serializer.rb
@@ -4,19 +4,13 @@
require 'active_support/core_ext/object/try'
require 'active_support/core_ext/string/inflections'
-require 'oj'
-require 'oj_serializers/memo'
-require 'oj_serializers/json_value'
+require 'json_serializers/memo'
+require 'json_serializers/json_value'
# Public: Implementation of an "ActiveModelSerializer"-like DSL, but with a
# design that allows replacing the internal object, which greatly reduces object
# allocation.
-#
-# Unlike ActiveModelSerializer, which builds a Hash which then gets encoded to
-# JSON, this implementation allows to use Oj::StringWriter to write directly to
-# JSON, greatly reducing the overhead of allocating and garbage collecting the
-# hashes.
-class OjSerializers::Serializer
+class JsonSerializers::Serializer
# Public: Used to validate incorrect memoization during development. Users of
# this library might add additional options as needed.
ALLOWED_INSTANCE_VARIABLES = %w[
@@ -36,8 +30,14 @@ class OjSerializers::Serializer
serializer
].to_set
- CACHE = (defined?(Rails) && Rails.cache) ||
- (defined?(ActiveSupport::Cache::MemoryStore) ? ActiveSupport::Cache::MemoryStore.new : OjSerializers::Memo.new)
+ CACHE = begin
+ (defined?(Rails) && Rails.cache) || begin
+ require 'active_support/cache'
+ ActiveSupport::Cache::MemoryStore.new
+ end
+ rescue LoadError
+ JsonSerializers::Memo.new
+ end
# Internal: The environment the app is currently running on.
environment = ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'production'
@@ -63,50 +63,14 @@ def _check_instance_variables
end
end
- # Internal: Used internally to write a single object to JSON.
- #
- # writer - writer used to serialize results
- # item - item to serialize results for
- # options - list of external options to pass to the serializer (available as `options`)
- #
- # NOTE: Binds this instance to the specified object and options and writes
- # to json using the provided writer.
- def write_one(writer, item, options = nil)
- writer.push_object
- write_to_json(writer, item, options)
- writer.pop
- end
-
- # Internal: Used internally to write an array of objects to JSON.
- #
- # writer - writer used to serialize results
- # items - items to serialize results for
- # options - list of external options to pass to the serializer (available as `options`)
- def write_many(writer, items, options = nil)
- writer.push_array
- items.each do |item|
- write_one(writer, item, options)
- end
- writer.pop
- end
-
protected
# Internal: An internal cache that can be used for temporary memoization.
def memo
- @memo ||= OjSerializers::Memo.new
+ @memo ||= JsonSerializers::Memo.new
end
class << self
- # Public: Allows the user to specify `default_format :json`, as a simple
- # way to ensure that `.one` and `.many` work as in Version 1.
- #
- # This setting is inherited from parent classes.
- def default_format(format)
- define_singleton_method(:_default_format) { format }
- define_serialization_shortcuts
- end
-
# Public: Allows to sort fields by name instead of by definition order, or
# pass a Proc to apply a custom order.
#
@@ -147,18 +111,6 @@ def object_as(name, **)
# NOTE: `one` serves as a replacement for `new` in these serializers.
private :new
- # Internal: Delegates to the instance methods, the advantage is that we can
- # reuse the same serializer instance to serialize different objects.
- delegate :write_one, :write_many, :write_to_json, to: :instance
-
- # Internal: Keep a reference to the default `write_one` method so that we
- # can use it inside cached overrides and benchmark tests.
- alias_method :non_cached_write_one, :write_one
-
- # Internal: Keep a reference to the default `write_many` method so that we
- # can use it inside cached overrides and benchmark tests.
- alias_method :non_cached_write_many, :write_many
-
# Helper: Serializes one or more items.
def render(item, options = nil)
many?(item) ? many(item, options) : one(item, options)
@@ -174,30 +126,6 @@ def one_if(item, options = nil)
one(item, options) if item
end
- # Public: Serializes the configured attributes for the specified object.
- #
- # item - the item to serialize
- # options - list of external options to pass to the sub class (available in `item.options`)
- #
- # Returns an Oj::StringWriter instance, which is encoded as raw json.
- def one_as_json(item, options = nil)
- writer = new_json_writer
- write_one(writer, item, options)
- writer
- end
-
- # Public: Serializes an array of items using this serializer.
- #
- # items - Must respond to `each`.
- # options - list of external options to pass to the sub class (available in `item.options`)
- #
- # Returns an Oj::StringWriter instance, which is encoded as raw json.
- def many_as_json(items, options = nil)
- writer = new_json_writer
- write_many(writer, items, options)
- writer
- end
-
# Public: Renders the configured attributes for the specified object,
# without serializing to JSON.
#
@@ -247,8 +175,7 @@ def item_cache_key(item, cache_key_proc)
#
# NOTE: Benchmark it, sometimes caching is actually SLOWER.
def cached(cache_key_proc = :cache_key.to_proc)
- cache_options = { namespace: "#{name}#write_to_json", version: OjSerializers::VERSION }.freeze
- cache_hash_options = { namespace: "#{name}#render_as_hash", version: OjSerializers::VERSION }.freeze
+ cache_hash_options = { namespace: "#{name}#render_as_hash", version: JsonSerializers::VERSION }.freeze
# Internal: Redefine `one_as_hash` to use the cache for the serialized hash.
define_singleton_method(:one_as_hash) do |item, options = nil|
@@ -279,58 +206,13 @@ def cached(cache_key_proc = :cache_key.to_proc)
end.values
end
- # Internal: Redefine `write_one` to use the cache for the serialized JSON.
- define_singleton_method(:write_one) do |external_writer, item, options = nil|
- cached_item = CACHE.fetch(item_cache_key(item, cache_key_proc), cache_options) do
- writer = new_json_writer
- non_cached_write_one(writer, item, options)
- writer.to_json
- end
- external_writer.push_json("#{cached_item}\n") # Oj.dump expects a new line terminator.
- end
-
- # Internal: Redefine `write_many` to use fetch_multi from cache.
- define_singleton_method(:write_many) do |external_writer, items, options = nil|
- # We define a one-off method for the class to receive the entire object
- # inside the `fetch_multi` block. Otherwise we would only get the cache
- # key, and we would need to build a Hash to retrieve the object.
- #
- # NOTE: The assignment is important, as queries would return different
- # objects when expanding with the splat in fetch_multi.
- items = items.entries.each do |item|
- item_key = item_cache_key(item, cache_key_proc)
- item.define_singleton_method(:cache_key) { item_key }
- end
-
- # Fetch all items at once by leveraging `read_multi`.
- #
- # NOTE: Memcached does not support `write_multi`, if we switch the cache
- # store to use Redis performance would improve a lot for this case.
- cached_items = CACHE.fetch_multi(*items, cache_options) do |item|
- writer = new_json_writer
- non_cached_write_one(writer, item, options)
- writer.to_json
- end.values
- external_writer.push_json("#{OjSerializers::JsonValue.array(cached_items)}\n") # Oj.dump expects a new line terminator.
- end
-
define_serialization_shortcuts
end
alias_method :cached_with_key, :cached
- def define_serialization_shortcuts(format = _default_format)
- case format
- when :json, :hash
- singleton_class.alias_method :one, :"one_as_#{format}"
- singleton_class.alias_method :many, :"many_as_#{format}"
- else
- raise ArgumentError, "Unknown serialization format: #{format.inspect}"
- end
- end
-
- # Internal: The writer to use to write to json
- def new_json_writer
- Oj::StringWriter.new(mode: :rails)
+ def define_serialization_shortcuts
+ singleton_class.alias_method :one, :one_as_hash
+ singleton_class.alias_method :many, :many_as_hash
end
# Public: Identifiers are always serialized first.
@@ -472,32 +354,6 @@ def many?(item)
(defined?(Mongoid::Association::Many) && item.is_a?(Mongoid::Association::Many))
end
- # Internal: We generate code for the serializer to avoid the overhead of
- # using variables for method names, having to iterate the list of attributes
- # and associations, and the overhead of using `send` with dynamic methods.
- #
- # As a result, the performance is the same as writing the most efficient
- # code by hand.
- def code_to_write_to_json(attributes)
- <<~WRITE_TO_JSON
- # Public: Writes this serializer content to a provided Oj::StringWriter.
- def write_to_json(writer, item, options = nil)
- @object = item
- @options = options
- @memo.clear if defined?(@memo)
- #{ attributes.map { |key, options|
- code_to_write_conditionally(options) {
- if options[:association]
- code_to_write_association(key, options)
- else
- code_to_write_attribute(key, options)
- end
- }
- }.join("\n ") }#{code_to_rescue_no_method if DEV_MODE}
- end
- WRITE_TO_JSON
- end
-
# Internal: We generate code for the serializer to avoid the overhead of
# using variables for method names, having to iterate the list of attributes
# and associations, and the overhead of using `send` with dynamic methods.
@@ -558,72 +414,6 @@ def check_conditional_method(options)
include_method_name if method_defined?(include_method_name)
end
- # Internal: Returns the code to render an attribute or association
- # conditionally.
- #
- # NOTE: Detects any include methods defined in the serializer, or defines
- # one by using the lambda passed in the `if` option, if any.
- def code_to_write_conditionally(options)
- if (include_method_name = check_conditional_method(options))
- "if #{include_method_name};#{yield};end\n"
- else
- yield
- end
- end
-
- # Internal: Returns the code for the association method.
- def code_to_write_attribute(key, options)
- value_from = options.fetch(:value_from)
-
- value = case (strategy = options.fetch(:attribute))
- when :serializer
- # Obtains the value by calling a method in the serializer.
- value_from
- when :method
- # Obtains the value by calling a method in the object, and writes it.
- "@object.#{value_from}"
- when :hash
- # Writes a Hash value to JSON, works with String or Symbol keys.
- "@object[#{value_from.inspect}]"
- when :mongoid
- # Writes an Mongoid attribute to JSON, this is the fastest strategy.
- "@object.attributes['#{value_from}']"
- else
- raise ArgumentError, "Unknown attribute strategy: #{strategy.inspect}"
- end
-
- "writer.push_value(#{value}, #{key.inspect})"
- end
-
- # Internal: Returns the code for the association method.
- def code_to_write_association(key, options)
- # Use a serializer method if defined, else call the association in the object.
- value_from = options.fetch(:value_from)
- value = method_defined?(value_from) ? value_from : "@object.#{value_from}"
- serializer_class = options.fetch(:serializer)
-
- case type = options.fetch(:association)
- when :one
- <<~WRITE_ONE
- if __value = #{value}
- writer.push_key('#{key}')
- #{serializer_class}.write_one(writer, __value, options)
- end
- WRITE_ONE
- when :many
- <<~WRITE_MANY
- writer.push_key('#{key}')
- #{serializer_class}.write_many(writer, #{value}, options)
- WRITE_MANY
- when :flat
- <<~WRITE_FLAT
- #{serializer_class}.write_to_json(writer, #{value}, options)
- WRITE_FLAT
- else
- raise ArgumentError, "Unknown association type: #{type.inspect}"
- end
- end
-
# Internal: Returns the code to render an attribute or association
# conditionally.
#
@@ -696,11 +486,10 @@ def instance_key
end
end
- # Internal: Generates write_to_json and render_as_hash methods optimized for
+ # Internal: Generates the render_as_hash method optimized for
# the specified configuration.
def prepare_serializer
attributes = prepare_attributes
- class_eval(code_to_write_to_json(attributes))
class_eval(code_to_render_as_hash(attributes))
end
@@ -726,7 +515,15 @@ def prepare_attributes(transform_keys: try(:_transform_keys), sort_by: try(:_sor
end
end
- default_format :hash
+ define_serialization_shortcuts
end
-Oj::Serializer = OjSerializers::Serializer unless defined?(Oj::Serializer)
+# Public: Top-level alias for convenience.
+JsonSerializer = JsonSerializers::Serializer unless defined?(JsonSerializer)
+
+# Backwards Compatibility: Keep Oj::Serializer alias so existing serializers
+# inheriting from Oj::Serializer continue to work.
+unless defined?(Oj::Serializer)
+ Object.const_set(:Oj, Module.new) unless defined?(Oj)
+ Oj::Serializer = JsonSerializers::Serializer
+end
diff --git a/lib/json_serializers/sugar.rb b/lib/json_serializers/sugar.rb
new file mode 100644
index 0000000..eb2506b
--- /dev/null
+++ b/lib/json_serializers/sugar.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'rails/railtie'
+require 'action_controller'
+require 'action_controller/railtie'
+
+require 'json_serializers'
+require 'json_serializers/controller_serialization'
+
+# Internal: Allows to pass JsonSerializer as options in `render`.
+class JsonSerializers::Railtie < Rails::Railtie
+ initializer 'json_serializers.action_controller' do
+ ActiveSupport.on_load(:action_controller) do
+ include(JsonSerializers::ControllerSerialization)
+ end
+ end
+end
diff --git a/lib/oj_serializers/version.rb b/lib/json_serializers/version.rb
similarity index 70%
rename from lib/oj_serializers/version.rb
rename to lib/json_serializers/version.rb
index 6a2b3fe..80f6d90 100644
--- a/lib/oj_serializers/version.rb
+++ b/lib/json_serializers/version.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-module OjSerializers
+module JsonSerializers
VERSION = '3.0.0'
end
diff --git a/lib/oj_serializers.rb b/lib/oj_serializers.rb
deleted file mode 100644
index a085953..0000000
--- a/lib/oj_serializers.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'oj'
-require 'oj_serializers/version'
-require 'oj_serializers/setup'
-require 'oj_serializers/serializer'
diff --git a/lib/oj_serializers/setup.rb b/lib/oj_serializers/setup.rb
deleted file mode 100644
index c75938c..0000000
--- a/lib/oj_serializers/setup.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-# frozen_string_literal: true
-
-require 'oj'
-
-# NOTE: We automatically set the necessary configuration unless it had been
-# explicitly set beforehand.
-unless Oj.default_options[:use_raw_json]
- require 'rails'
- Oj.optimize_rails
- Oj.default_options = { mode: :rails, use_raw_json: true }
-end
-
-# NOTE: Add an optimization to make it easier to work with a StringWriter
-# transparently in different scenarios.
-class Oj::StringWriter
- alias original_as_json as_json
-
- # Internal: ActiveSupport can pass an options argument to `as_json` when
- # serializing a Hash or Array.
- def as_json(_options = nil)
- original_as_json
- end
-
- # Internal: We can use `to_s` directly, this is not important but gives a
- # slight boost to a few use cases that use it for caching in Memcached.
- def to_json(_options = nil)
- to_s.delete_suffix("\n")
- end
-end
diff --git a/lib/oj_serializers/sugar.rb b/lib/oj_serializers/sugar.rb
deleted file mode 100644
index 8a59195..0000000
--- a/lib/oj_serializers/sugar.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails/railtie'
-require 'action_controller'
-require 'action_controller/railtie'
-
-require 'oj_serializers'
-require 'oj_serializers/controller_serialization'
-
-# Internal: Allows to pass Oj serializers as options in `render`.
-class OjSerializers::Railtie < Rails::Railtie
- initializer 'oj_serializers.action_controller' do
- ActiveSupport.on_load(:action_controller) do
- include(OjSerializers::ControllerSerialization)
- end
- end
-end
diff --git a/spec/benchmark_helper.rb b/spec/benchmark_helper.rb
index ba44e35..21ed052 100644
--- a/spec/benchmark_helper.rb
+++ b/spec/benchmark_helper.rb
@@ -11,7 +11,7 @@
require 'rails'
require 'active_support/json'
require 'active_support/core_ext/time/zones'
-require 'oj_serializers/compat'
+require 'json_serializers/compat'
Time.zone = 'UTC'
diff --git a/spec/oj_serializers/associations_spec.rb b/spec/json_serializers/associations_spec.rb
similarity index 100%
rename from spec/oj_serializers/associations_spec.rb
rename to spec/json_serializers/associations_spec.rb
diff --git a/spec/oj_serializers/caching_spec.rb b/spec/json_serializers/caching_spec.rb
similarity index 63%
rename from spec/oj_serializers/caching_spec.rb
rename to spec/json_serializers/caching_spec.rb
index 08ce73a..73ab145 100644
--- a/spec/oj_serializers/caching_spec.rb
+++ b/spec/json_serializers/caching_spec.rb
@@ -21,7 +21,7 @@ class CachedAlbumSerializer < AlbumSerializer
before do
# NOTE: Uncomment to debug test failures.
- # Oj::Serializer::CACHE.logger = ActiveSupport::Logger.new(STDOUT)
+ # JsonSerializers::Serializer::CACHE.logger = ActiveSupport::Logger.new(STDOUT)
end
it 'should reuse the cache effectively' do
@@ -46,21 +46,4 @@ class CachedAlbumSerializer < AlbumSerializer
expect_parsed_json(CachedAlbumSerializer.one(other_album)).to eq other_attrs
expect_parsed_json(CachedAlbumSerializer.many(albums)).to eq [attrs, other_attrs]
end
-
- it 'should reuse the cache effectively for JSON' do
- attrs = parse_json(AlbumSerializer.one_as_json(album))
- expect(attrs).to include(name: album.name)
- other_attrs = parse_json(AlbumSerializer.one_as_json(other_album))
- expect(other_attrs).to eq(name: 'Amigos', release: 'March 26, 1976', genres: nil, songs: [])
-
- expect(album).to receive(:release_date).once.and_call_original
- expect(other_album).to receive(:release_date).once.and_call_original
- expect_parsed_json(CachedAlbumSerializer.one_as_json(album)).to eq attrs
- expect_parsed_json(CachedAlbumSerializer.one_as_json(other_album)).to eq other_attrs
-
- expect_any_instance_of(Album).not_to receive(:release_date)
- expect_parsed_json(CachedAlbumSerializer.one_as_json(album)).to eq attrs
- expect_parsed_json(CachedAlbumSerializer.one_as_json(other_album)).to eq other_attrs
- expect_parsed_json(CachedAlbumSerializer.many_as_json(albums)).to eq [attrs, other_attrs]
- end
end
diff --git a/spec/oj_serializers/compat_spec.rb b/spec/json_serializers/compat_spec.rb
similarity index 67%
rename from spec/oj_serializers/compat_spec.rb
rename to spec/json_serializers/compat_spec.rb
index 55901dd..c6ca7f9 100644
--- a/spec/oj_serializers/compat_spec.rb
+++ b/spec/json_serializers/compat_spec.rb
@@ -5,18 +5,14 @@
require 'support/models/album'
require 'support/serializers/active_model_serializer'
-class CompatSerializer < Oj::Serializer
+class CompatSerializer < JsonSerializer
has_one :item, key: :album, serializer: ActiveModelSerializer
has_many :items, serializer: ActiveModelSerializer, unless: -> { options[:skip_collection] }
end
-class JsonCompatSerializer < CompatSerializer
- default_format :json
-end
-
RSpec.describe 'AMS Compat', type: :serializer do
def expect_encoded_json(object)
- expect(Oj.dump(object).tr("\n", ''))
+ expect(JSON.generate(object))
end
it 'can use ams serializer in associations' do
@@ -32,14 +28,5 @@ def expect_encoded_json(object)
expect_encoded_json(CompatSerializer.one(object, skip_collection: true)).to eq({
album: attrs,
}.to_json)
-
- expect_encoded_json(JsonCompatSerializer.one(object)).to eq({
- album: attrs,
- items: [attrs, attrs],
- }.to_json)
-
- expect_encoded_json(JsonCompatSerializer.one(object, skip_collection: true)).to eq({
- album: attrs,
- }.to_json)
end
end
diff --git a/spec/oj_serializers/dev_mode_spec.rb b/spec/json_serializers/dev_mode_spec.rb
similarity index 88%
rename from spec/oj_serializers/dev_mode_spec.rb
rename to spec/json_serializers/dev_mode_spec.rb
index 686b7f2..ae99861 100644
--- a/spec/oj_serializers/dev_mode_spec.rb
+++ b/spec/json_serializers/dev_mode_spec.rb
@@ -4,7 +4,7 @@
require 'support/models/album'
require 'support/serializers/invalid_album_serializer'
-class StatefulSerializer < Oj::Serializer
+class StatefulSerializer < JsonSerializer
hash_attributes 'genre'
attribute
@@ -13,7 +13,7 @@ def name
end
end
-class MissingAttributeSerializer < Oj::Serializer
+class MissingAttributeSerializer < JsonSerializer
mongo_attributes(:name2)
end
@@ -21,7 +21,7 @@ class MissingAttributeSerializer < Oj::Serializer
let(:album) { Album.abraxas }
before do
- expect(Oj::Serializer::DEV_MODE).to eq true
+ expect(JsonSerializers::Serializer::DEV_MODE).to eq true
end
it 'should fail early when memoization is used incorrectly' do
diff --git a/spec/oj_serializers/json_string_encoder_spec.rb b/spec/json_serializers/json_string_encoder_spec.rb
similarity index 89%
rename from spec/oj_serializers/json_string_encoder_spec.rb
rename to spec/json_serializers/json_string_encoder_spec.rb
index ba972ae..0b212bf 100644
--- a/spec/oj_serializers/json_string_encoder_spec.rb
+++ b/spec/json_serializers/json_string_encoder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-require 'oj_serializers/json_string_encoder'
+require 'json_serializers/json_string_encoder'
class CustomValue
def as_json(*)
@@ -10,13 +10,13 @@ def as_json(*)
end
end
-RSpec.describe OjSerializers::JsonStringEncoder, type: :serializer do
+RSpec.describe JsonSerializers::JsonStringEncoder, type: :serializer do
def expect_encoded_json(object, options = {})
- expect(OjSerializers::JsonStringEncoder.encode_to_json(object, **options).tr("\n", ''))
+ expect(JsonSerializers::JsonStringEncoder.encode_to_json(object, **options).tr("\n", ''))
end
def expect_incorrect_usage(object, options = {})
- expect { OjSerializers::JsonStringEncoder.encode_to_json(object, **options) }
+ expect { JsonSerializers::JsonStringEncoder.encode_to_json(object, **options) }
end
let(:hash) { { a: 1, b: '2', c: nil, d: false, e: BSON::ObjectId.new, f: CustomValue.new } }
@@ -41,17 +41,17 @@ def expect_incorrect_usage(object, options = {})
it 'should not double encode JsonValue' do
json_string = hash.to_json
- complex = [{ complex: OjSerializers::JsonValue.new(json_string) }]
+ complex = [{ complex: JsonSerializers::JsonValue.new(json_string) }]
expect_encoded_json(complex).to eq([{ complex: hash }].to_json)
expect_encoded_json(complex, root: :mixed).to eq({ mixed: [{ complex: hash }] }.to_json)
json_strings = [json_string, json_string]
- complex_array = [{ complex_array: OjSerializers::JsonValue.array(json_strings) }]
+ complex_array = [{ complex_array: JsonSerializers::JsonValue.array(json_strings) }]
expect_encoded_json(complex_array).to eq([{ complex_array: [hash, hash] }].to_json)
expect_encoded_json(complex_array, root: :mixed).to eq({ mixed: [{ complex_array: [hash, hash] }] }.to_json)
- expect(complex.as_json.to_json).to eq([{ complex: hash }].to_json)
- expect(OjSerializers::JsonValue.new(json_string).to_s).to eq json_string
+ expect(JSON.generate(complex.as_json)).to eq([{ complex: hash }].to_json)
+ expect(JsonSerializers::JsonValue.new(json_string).to_s).to eq json_string
end
end
diff --git a/spec/oj_serializers/legacy_mode_spec.rb b/spec/json_serializers/legacy_mode_spec.rb
similarity index 97%
rename from spec/oj_serializers/legacy_mode_spec.rb
rename to spec/json_serializers/legacy_mode_spec.rb
index 2d9f3df..5f954c2 100644
--- a/spec/oj_serializers/legacy_mode_spec.rb
+++ b/spec/json_serializers/legacy_mode_spec.rb
@@ -23,7 +23,7 @@ def songs
end
end
-class OjAlbumSerializer < Oj::Serializer
+class OjAlbumSerializer < JsonSerializer
object_as :album
ams_attributes(
diff --git a/spec/oj_serializers/memo_spec.rb b/spec/json_serializers/memo_spec.rb
similarity index 90%
rename from spec/oj_serializers/memo_spec.rb
rename to spec/json_serializers/memo_spec.rb
index 3f59268..1d013e4 100644
--- a/spec/oj_serializers/memo_spec.rb
+++ b/spec/json_serializers/memo_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe OjSerializers::Memo do
+RSpec.describe JsonSerializers::Memo do
let(:memo) { described_class.new }
it 'should memoize the values if they were not available' do
diff --git a/spec/oj_serializers/sort_attributes_spec.rb b/spec/json_serializers/sort_attributes_spec.rb
similarity index 100%
rename from spec/oj_serializers/sort_attributes_spec.rb
rename to spec/json_serializers/sort_attributes_spec.rb
diff --git a/spec/oj_serializers/sugar_spec.rb b/spec/json_serializers/sugar_spec.rb
similarity index 100%
rename from spec/oj_serializers/sugar_spec.rb
rename to spec/json_serializers/sugar_spec.rb
diff --git a/spec/oj_serializers/transform_keys_spec.rb b/spec/json_serializers/transform_keys_spec.rb
similarity index 100%
rename from spec/oj_serializers/transform_keys_spec.rb
rename to spec/json_serializers/transform_keys_spec.rb
diff --git a/spec/json_serializers/version_spec.rb b/spec/json_serializers/version_spec.rb
new file mode 100644
index 0000000..b050733
--- /dev/null
+++ b/spec/json_serializers/version_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+RSpec.describe JsonSerializers do
+ it 'has a version number' do
+ expect(JsonSerializers::VERSION).not_to be nil
+ end
+end
diff --git a/spec/oj_serializers/version_spec.rb b/spec/oj_serializers/version_spec.rb
deleted file mode 100644
index cefef2f..0000000
--- a/spec/oj_serializers/version_spec.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe OjSerializers do
- it 'has a version number' do
- expect(OjSerializers::VERSION).not_to be nil
- end
-end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 37e4446..2c387e1 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -6,7 +6,7 @@
SimpleCov.start { add_filter '/spec/' }
require 'bundler/setup'
-require 'oj_serializers'
+require 'json_serializers'
require 'bson'
class BSON::ObjectId
diff --git a/spec/support/controllers/application.rb b/spec/support/controllers/application.rb
index 73515a5..7091b82 100644
--- a/spec/support/controllers/application.rb
+++ b/spec/support/controllers/application.rb
@@ -7,12 +7,23 @@
class MusicApplication < Rails::Application
def quick_setup
- initializers.find { |i| i.name == 'active_model_serializers.action_controller' }.run
- initializers.find { |i| i.name == 'oj_serializers.action_controller' }.run
+ ams_init = initializers.find { |i| i.name == 'active_model_serializers.action_controller' }
+ oj_init = initializers.find { |i| i.name == 'json_serializers.action_controller' }
+
+ if ams_init && oj_init
+ ams_init.run
+ oj_init.run
+ else
+ # If initializers weren't registered (e.g. due to load order), manually
+ # include the modules needed for the controller tests.
+ require 'action_controller/serialization'
+ ApplicationController.include(::ActionController::Serialization)
+ ApplicationController.include(JsonSerializers::ControllerSerialization)
+ end
end
end
class ApplicationController < ActionController::Base
end
-require 'oj_serializers/sugar'
+require 'json_serializers/sugar'
diff --git a/spec/support/models/sql.rb b/spec/support/models/sql.rb
index 04ec673..d6ce166 100644
--- a/spec/support/models/sql.rb
+++ b/spec/support/models/sql.rb
@@ -56,18 +56,18 @@ def full_name
end
end
-class PlayerSerializer < Oj::Serializer
+class PlayerSerializer < JsonSerializer
identifier
attributes :first_name, :last_name, :full_name
end
# NOTE: This example is quite contrived. Finding good test cases is as hard as
# finding good names.
-class ScoresSerializer < Oj::Serializer
+class ScoresSerializer < JsonSerializer
attributes :high_score, :score
end
-class GameSerializer < Oj::Serializer
+class GameSerializer < JsonSerializer
identifier
attributes :name
diff --git a/spec/support/non_blank_json_writer.rb b/spec/support/non_blank_json_writer.rb
deleted file mode 100644
index 574c112..0000000
--- a/spec/support/non_blank_json_writer.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-# Public: An example on how the writer can be modified to do funky stuff.
-class NonBlankJsonWriter < DelegateClass(Oj::StringWriter)
- def self.new
- super(Oj::StringWriter.new(mode: :rails))
- end
-
- def push_value(value, key = nil)
- super if value.present?
- end
-
- # Internal: Used by Oj::Rails::Encoder because we use the `raw_json` option.
- def raw_json(*)
- # We need to pass no arguments to `Oj::StringWriter` because it expects 0 arguments
- # because its method definition `oj_dump_raw_json` defined in the C classes is defined
- # without arguments. Oj gets confused because it checks if the class is `Oj::StringWriter`
- # and if it is, then it passes 0 arguments, but when it's not (e.g. `NonBlankJsonWriter`)
- # then it passes both. So in this case, we're calling super() to `Oj::StringWriter` with
- # two arguments.
- #
- # https://github.com/ohler55/oj/commit/d0820d2ac1a72584329bc6451d430737a27f99ac#diff-854d0b67397d7006482043d1202c9647R532
- super()
- end
-end
diff --git a/spec/support/serializers/active_model_serializer.rb b/spec/support/serializers/active_model_serializer.rb
index fae4584..8b31427 100644
--- a/spec/support/serializers/active_model_serializer.rb
+++ b/spec/support/serializers/active_model_serializer.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'active_model_serializers'
-require 'oj_serializers/compat'
+require 'json_serializers/compat'
class ActiveModelSerializer < ActiveModel::Serializer
attributes(
diff --git a/spec/support/serializers/album_serializer.rb b/spec/support/serializers/album_serializer.rb
index b82dce9..5702a2e 100644
--- a/spec/support/serializers/album_serializer.rb
+++ b/spec/support/serializers/album_serializer.rb
@@ -2,7 +2,7 @@
require_relative 'song_serializer'
-class AlbumSerializer < Oj::Serializer
+class AlbumSerializer < JsonSerializer
transform_keys :camelize
mongo_attributes(
diff --git a/spec/support/serializers/blueprints.rb b/spec/support/serializers/blueprints.rb
index a6cdd2f..5773071 100644
--- a/spec/support/serializers/blueprints.rb
+++ b/spec/support/serializers/blueprints.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'blueprinter'
-require 'oj_serializers/compat'
+require 'json_serializers/compat'
Blueprinter.configure do |config|
config.sort_fields_by = :definition
diff --git a/spec/support/serializers/invalid_album_serializer.rb b/spec/support/serializers/invalid_album_serializer.rb
index c36323f..ae82bc4 100644
--- a/spec/support/serializers/invalid_album_serializer.rb
+++ b/spec/support/serializers/invalid_album_serializer.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class InvalidAlbumSerializer < Oj::Serializer
+class InvalidAlbumSerializer < JsonSerializer
attributes :release
def release
diff --git a/spec/support/serializers/legacy_serializers.rb b/spec/support/serializers/legacy_serializers.rb
index ded8cd1..e8be34b 100644
--- a/spec/support/serializers/legacy_serializers.rb
+++ b/spec/support/serializers/legacy_serializers.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'active_model_serializers'
-require 'oj_serializers/compat'
+require 'json_serializers/compat'
# https://github.com/rails-api/active_model_serializers/blob/0-8-stable/README.md#1-disable-root-globally-for-all-or-per-class
ActiveSupport.on_load(:active_model_serializers) do
diff --git a/spec/support/serializers/model_serializer.rb b/spec/support/serializers/model_serializer.rb
index 8f42a07..55ad72f 100644
--- a/spec/support/serializers/model_serializer.rb
+++ b/spec/support/serializers/model_serializer.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ModelSerializer < Oj::Serializer
+class ModelSerializer < JsonSerializer
attributes(
:id,
:name,
diff --git a/spec/support/serializers/option_serializer.rb b/spec/support/serializers/option_serializer.rb
index da543cc..a44f5ed 100644
--- a/spec/support/serializers/option_serializer.rb
+++ b/spec/support/serializers/option_serializer.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'oj_serializers'
+require 'json_serializers'
require 'active_model_serializers'
require 'blueprinter'
require 'panko_serializer'
@@ -44,7 +44,7 @@ class Blueprinter < Blueprinter::Base
end
end
- class Oj < Oj::Serializer
+ class Oj < JsonSerializer
attr
def label
@object.attributes['name']
@@ -68,22 +68,6 @@ def value
end
end
- def self.write_models(models)
- writer = ::Oj::StringWriter.new(mode: :wab)
-
- writer.push_array
-
- models.each do |model|
- writer.push_object
- writer.push_value(model.attributes['name'], 'label')
- writer.push_value(model.attributes['_id'], 'value')
- writer.pop
- end
- writer.pop
-
- writer
- end
-
def self.map_models(models)
models.map do |model|
{ label: model.attributes['name'], value: model.attributes['_id'] }
diff --git a/spec/support/serializers/song_serializer.rb b/spec/support/serializers/song_serializer.rb
index 13f7c00..bc473b3 100644
--- a/spec/support/serializers/song_serializer.rb
+++ b/spec/support/serializers/song_serializer.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class SongSerializer < Oj::Serializer
+class SongSerializer < JsonSerializer
mongo_attributes(
:id,
:track,