oj_serializers 1.0.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/README.md +364 -195
- data/lib/oj_serializers/compat.rb +4 -19
- data/lib/oj_serializers/controller_serialization.rb +2 -2
- data/lib/oj_serializers/json_string_encoder.rb +12 -28
- data/lib/oj_serializers/serializer.rb +389 -156
- data/lib/oj_serializers/version.rb +1 -1
- metadata +6 -33
data/README.md
CHANGED
@@ -6,29 +6,33 @@ Oj Serializers
|
|
6
6
|
<a href="https://codeclimate.com/github/ElMassimo/oj_serializers"><img alt="Maintainability" src="https://codeclimate.com/github/ElMassimo/oj_serializers/badges/gpa.svg"/></a>
|
7
7
|
<a href="https://codeclimate.com/github/ElMassimo/oj_serializers"><img alt="Test Coverage" src="https://codeclimate.com/github/ElMassimo/oj_serializers/badges/coverage.svg"/></a>
|
8
8
|
<a href="https://rubygems.org/gems/oj_serializers"><img alt="Gem Version" src="https://img.shields.io/gem/v/oj_serializers.svg?colorB=e9573f"/></a>
|
9
|
-
<a href="https://github.com/ElMassimo/oj_serializers/blob/
|
9
|
+
<a href="https://github.com/ElMassimo/oj_serializers/blob/main/LICENSE.txt"><img alt="License" src="https://img.shields.io/badge/license-MIT-428F7E.svg"/></a>
|
10
10
|
</p>
|
11
11
|
</h1>
|
12
12
|
|
13
|
-
JSON serializers for Ruby, built on top of the powerful [`oj`][oj] library.
|
13
|
+
Faster JSON serializers for Ruby, built on top of the powerful [`oj`][oj] library.
|
14
14
|
|
15
15
|
[oj]: https://github.com/ohler55/oj
|
16
16
|
[mongoid]: https://github.com/mongodb/mongoid
|
17
17
|
[ams]: https://github.com/rails-api/active_model_serializers
|
18
18
|
[jsonapi]: https://github.com/jsonapi-serializer/jsonapi-serializer
|
19
19
|
[panko]: https://github.com/panko-serializer/panko_serializer
|
20
|
+
[blueprinter]: https://github.com/procore/blueprinter
|
20
21
|
[benchmarks]: https://github.com/ElMassimo/oj_serializers/tree/master/benchmarks
|
21
|
-
[raw_benchmarks]: https://github.com/ElMassimo/oj_serializers/blob/
|
22
|
-
[sugar]: https://github.com/ElMassimo/oj_serializers/blob/
|
23
|
-
[migration guide]: https://github.com/ElMassimo/oj_serializers/blob/
|
22
|
+
[raw_benchmarks]: https://github.com/ElMassimo/oj_serializers/blob/main/benchmarks/document_benchmark.rb
|
23
|
+
[sugar]: https://github.com/ElMassimo/oj_serializers/blob/main/lib/oj_serializers/sugar.rb#L14
|
24
|
+
[migration guide]: https://github.com/ElMassimo/oj_serializers/blob/main/MIGRATION_GUIDE.md
|
24
25
|
[design]: https://github.com/ElMassimo/oj_serializers#design-
|
25
26
|
[raw_json]: https://github.com/ohler55/oj/issues/542
|
26
27
|
[trailing_commas]: https://maximomussini.com/posts/trailing-commas/
|
28
|
+
[render dsl]: https://github.com/ElMassimo/oj_serializers#render-dsl-
|
29
|
+
[sorbet]: https://sorbet.org/
|
30
|
+
[Discussion]: https://github.com/ElMassimo/oj_serializers/discussions
|
27
31
|
|
28
32
|
## Why? 🤔
|
29
33
|
|
30
|
-
[`ActiveModel::Serializer`][ams] has a nice DSL, but it allocates many objects
|
31
|
-
to memory bloat, time spent on GC, and lower performance.
|
34
|
+
[`ActiveModel::Serializer`][ams] has a nice DSL, but it allocates many objects
|
35
|
+
leading to memory bloat, time spent on GC, and lower performance.
|
32
36
|
|
33
37
|
`Oj::Serializer` provides a similar API, with [better performance][benchmarks].
|
34
38
|
|
@@ -36,12 +40,11 @@ Learn more about [how this library achieves its performance][design].
|
|
36
40
|
|
37
41
|
## Features ⚡️
|
38
42
|
|
39
|
-
- Declaration syntax similar to Active Model Serializers
|
40
|
-
- Reduced memory allocation and [improved performance][benchmarks]
|
43
|
+
- Declaration syntax [similar to Active Model Serializers][migration guide]
|
44
|
+
- Reduced [memory allocation][benchmarks] and [improved performance][benchmarks]
|
41
45
|
- Support for `has_one` and `has_many`, compose with `flat_one`
|
42
46
|
- Useful development checks to avoid typos and mistakes
|
43
47
|
- Integrates nicely with Rails controllers
|
44
|
-
- Caching
|
45
48
|
|
46
49
|
## Installation 💿
|
47
50
|
|
@@ -58,13 +61,13 @@ And then run:
|
|
58
61
|
## Usage 🚀
|
59
62
|
|
60
63
|
You can define a serializer by subclassing `Oj::Serializer`, and specify which
|
61
|
-
attributes should be serialized
|
64
|
+
attributes should be serialized.
|
62
65
|
|
63
66
|
```ruby
|
64
67
|
class AlbumSerializer < Oj::Serializer
|
65
68
|
attributes :name, :genres
|
66
69
|
|
67
|
-
|
70
|
+
attr
|
68
71
|
def release
|
69
72
|
album.release_date.strftime('%B %d, %Y')
|
70
73
|
end
|
@@ -76,129 +79,84 @@ end
|
|
76
79
|
<details>
|
77
80
|
<summary>Example Output</summary>
|
78
81
|
|
79
|
-
```
|
82
|
+
```ruby
|
80
83
|
{
|
81
|
-
|
82
|
-
|
84
|
+
name: "Abraxas",
|
85
|
+
genres: [
|
83
86
|
"Pyschodelic Rock",
|
84
87
|
"Blues Rock",
|
85
88
|
"Jazz Fusion",
|
86
|
-
"Latin Rock"
|
89
|
+
"Latin Rock",
|
87
90
|
],
|
88
|
-
|
89
|
-
|
91
|
+
release: "September 23, 1970",
|
92
|
+
songs: [
|
90
93
|
{
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
"Michael Carabello"
|
95
|
-
]
|
94
|
+
track: 1,
|
95
|
+
name: "Sing Winds, Crying Beasts",
|
96
|
+
composers: ["Michael Carabello"],
|
96
97
|
},
|
97
98
|
{
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
"Peter Green",
|
102
|
-
"Gábor Szabó"
|
103
|
-
]
|
99
|
+
track: 2,
|
100
|
+
name: "Black Magic Woman / Gypsy Queen",
|
101
|
+
composers: ["Peter Green", "Gábor Szabó"],
|
104
102
|
},
|
105
103
|
{
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
"Tito Puente"
|
110
|
-
]
|
104
|
+
track: 3,
|
105
|
+
name: "Oye como va",
|
106
|
+
composers: ["Tito Puente"],
|
111
107
|
},
|
112
108
|
{
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
"Alberto Gianquinto",
|
117
|
-
"Carlos Santana"
|
118
|
-
]
|
109
|
+
track: 4,
|
110
|
+
name: "Incident at Neshabur",
|
111
|
+
composers: ["Alberto Gianquinto", "Carlos Santana"],
|
119
112
|
},
|
120
113
|
{
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
"José Areas"
|
125
|
-
]
|
114
|
+
track: 5,
|
115
|
+
name: "Se acabó",
|
116
|
+
composers: ["José Areas"],
|
126
117
|
},
|
127
118
|
{
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
"Gregg Rolie"
|
132
|
-
]
|
119
|
+
track: 6,
|
120
|
+
name: "Mother's Daughter",
|
121
|
+
composers: ["Gregg Rolie"],
|
133
122
|
},
|
134
123
|
{
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
"Santana"
|
139
|
-
]
|
124
|
+
track: 7,
|
125
|
+
name: "Samba pa ti",
|
126
|
+
composers: ["Santana"],
|
140
127
|
},
|
141
128
|
{
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
"Rolie"
|
146
|
-
]
|
129
|
+
track: 8,
|
130
|
+
name: "Hope You're Feeling Better",
|
131
|
+
composers: ["Rolie"],
|
147
132
|
},
|
148
133
|
{
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
}
|
155
|
-
]
|
134
|
+
track: 9,
|
135
|
+
name: "El Nicoya",
|
136
|
+
composers: ["Areas"],
|
137
|
+
},
|
138
|
+
],
|
156
139
|
}
|
157
140
|
```
|
158
141
|
</details>
|
159
142
|
|
160
|
-
|
161
|
-
|
162
|
-
To use the serializer, the recommended approach is:
|
143
|
+
You can then use your new serializer to render an object or collection:
|
163
144
|
|
164
145
|
```ruby
|
165
146
|
class AlbumsController < ApplicationController
|
166
147
|
def show
|
167
|
-
album = Album.find(params[:id])
|
168
148
|
render json: AlbumSerializer.one(album)
|
169
149
|
end
|
170
150
|
|
171
151
|
def index
|
172
|
-
albums = Album.all
|
173
152
|
render json: { albums: AlbumSerializer.many(albums) }
|
174
153
|
end
|
175
154
|
end
|
176
155
|
```
|
177
156
|
|
178
|
-
|
179
|
-
|
180
|
-
```ruby
|
181
|
-
require 'oj_serializers/sugar'
|
182
|
-
|
183
|
-
class AlbumsController < ApplicationController
|
184
|
-
def show
|
185
|
-
album = Album.find(params[:id])
|
186
|
-
render json: album, serializer: AlbumSerializer
|
187
|
-
end
|
188
|
-
|
189
|
-
def index
|
190
|
-
albums = Album.all
|
191
|
-
render json: albums, each_serializer: AlbumSerializer, root: :albums
|
192
|
-
end
|
193
|
-
end
|
194
|
-
```
|
195
|
-
|
196
|
-
It's recommended to create your own `BaseSerializer` class in order to easily
|
197
|
-
add custom extensions, specially when migrating from `active_model_serializers`.
|
198
|
-
|
199
|
-
## Render DSL 🛠
|
157
|
+
## Rendering 🖨
|
200
158
|
|
201
|
-
|
159
|
+
Use `one` to serialize objects, and `many` to serialize enumerables:
|
202
160
|
|
203
161
|
```ruby
|
204
162
|
render json: {
|
@@ -207,173 +165,212 @@ render json: {
|
|
207
165
|
}
|
208
166
|
```
|
209
167
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
## Attributes DSL 🛠
|
168
|
+
Serializers can be rendered arrays, hashes, or even inside `ActiveModel::Serializer`
|
169
|
+
by using a method in the serializer, making it very easy to combine with other
|
170
|
+
libraries and migrate incrementally.
|
215
171
|
|
216
|
-
|
217
|
-
to JSON. Each method provides a different strategy to obtain the values to serialize.
|
172
|
+
You can use `render` as a shortcut for `one` and `many`, but it might be less readable:
|
218
173
|
|
219
|
-
|
220
|
-
|
174
|
+
```ruby
|
175
|
+
render json: {
|
176
|
+
favorite_album: AlbumSerializer.render(album),
|
177
|
+
purchased_albums: AlbumSerializer.render(albums),
|
178
|
+
}
|
179
|
+
```
|
221
180
|
|
222
|
-
|
181
|
+
## Attributes DSL 🪄
|
223
182
|
|
224
|
-
|
183
|
+
Specify which attributes should be rendered by calling a method in the object to serialize.
|
225
184
|
|
226
185
|
```ruby
|
227
186
|
class PlayerSerializer < Oj::Serializer
|
228
|
-
attributes :full_name
|
187
|
+
attributes :first_name, :last_name, :full_name
|
229
188
|
end
|
230
189
|
```
|
231
190
|
|
232
|
-
|
233
|
-
account methods defined in the serializer. Being explicit about where the
|
234
|
-
attribute is coming from makes the serializers easier to understand and more
|
235
|
-
maintainable.
|
236
|
-
|
237
|
-
### `serializer_attributes`
|
238
|
-
|
239
|
-
Obtains the attribute value by calling a method defined in the serializer.
|
240
|
-
|
241
|
-
|
242
|
-
You may call [`serializer_attributes`](https://github.com/ElMassimo/oj_serializers/blob/master/spec/support/serializers/song_serializer.rb#L13-L15) or use the `attribute` inline syntax:
|
191
|
+
You can serialize custom values by specifying that a method is an `attribute`:
|
243
192
|
|
244
193
|
```ruby
|
245
194
|
class PlayerSerializer < Oj::Serializer
|
246
|
-
attribute
|
247
|
-
def full_name
|
195
|
+
attribute :name do
|
248
196
|
"#{player.first_name} #{player.last_name}"
|
249
197
|
end
|
250
|
-
end
|
251
|
-
```
|
252
|
-
|
253
|
-
Instance methods can access the object by the serializer name without the
|
254
|
-
`Serializer` suffix, `player` in the example above, or directly as `@object`.
|
255
|
-
|
256
|
-
You can customize this by using [`object_as`](https://github.com/ElMassimo/oj_serializers#using-a-different-alias-for-the-internal-object).
|
257
198
|
|
258
|
-
|
199
|
+
# or
|
259
200
|
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
class AlbumSerializer < Oj::Serializer
|
264
|
-
ams_attributes :name, :release
|
265
|
-
|
266
|
-
def release
|
267
|
-
album.release_date.strftime('%B %d, %Y')
|
201
|
+
attribute
|
202
|
+
def name
|
203
|
+
"#{player.first_name} #{player.last_name}"
|
268
204
|
end
|
269
205
|
end
|
270
206
|
```
|
271
207
|
|
272
|
-
|
208
|
+
> **Note**
|
209
|
+
>
|
210
|
+
> In this example, `player` was inferred from `PlayerSerializer`.
|
211
|
+
>
|
212
|
+
> You can customize this by using [`object_as`](#using-a-different-alias-for-the-internal-object).
|
273
213
|
|
274
|
-
Instead, use `attributes` for model methods, and the inline `attribute` for serializer attributes. Being explicit makes serializers easier to understand, and to maintain.
|
275
214
|
|
276
|
-
|
215
|
+
### Associations 🔗
|
277
216
|
|
278
|
-
|
217
|
+
Use `has_one` to serialize individual objects, and `has_many` to serialize a collection.
|
279
218
|
|
280
|
-
|
219
|
+
You must specificy which serializer to use with the `serializer` option.
|
281
220
|
|
282
221
|
```ruby
|
283
|
-
class
|
284
|
-
|
222
|
+
class SongSerializer < Oj::Serializer
|
223
|
+
has_one :album, serializer: AlbumSerializer
|
224
|
+
has_many :composers, serializer: ComposerSerializer
|
285
225
|
end
|
286
|
-
|
287
|
-
PersonSerializer.one('first_name' => 'Mary', :middle_name => 'Jane', :last_name => 'Watson')
|
288
|
-
# {"first_name":"Mary","last_name":"Watson"}
|
289
226
|
```
|
290
227
|
|
291
|
-
|
292
|
-
|
293
|
-
Reads data directly from `attributes` in a [Mongoid] document.
|
294
|
-
|
295
|
-
By skipping type casting, coercion, and defaults, it [achieves the best performance][raw_benchmarks].
|
296
|
-
|
297
|
-
Although there are some downsides, depending on how consistent your schema is,
|
298
|
-
and which kind of consumer the API has, it can be really powerful.
|
228
|
+
Specify a different value for the association by providing a block:
|
299
229
|
|
300
230
|
```ruby
|
301
|
-
class
|
302
|
-
|
231
|
+
class SongSerializer < Oj::Serializer
|
232
|
+
has_one :album, serializer: AlbumSerializer do
|
233
|
+
Album.find_by(song_ids: song.id)
|
234
|
+
end
|
303
235
|
end
|
304
236
|
```
|
305
237
|
|
306
|
-
|
238
|
+
In case you need to pass options, you can call the serializer manually:
|
307
239
|
|
308
|
-
|
240
|
+
```ruby
|
241
|
+
class SongSerializer < Oj::Serializer
|
242
|
+
attribute :album do
|
243
|
+
AlbumSerializer.one(song.album, for_song: song)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
```
|
309
247
|
|
310
|
-
|
248
|
+
### Aliasing or renaming attributes ↔️
|
311
249
|
|
312
|
-
You
|
250
|
+
You can pass `as` when defining an attribute or association to serialize it
|
251
|
+
using a different key:
|
313
252
|
|
314
253
|
```ruby
|
315
254
|
class SongSerializer < Oj::Serializer
|
316
|
-
has_one :album, serializer: AlbumSerializer
|
317
|
-
has_many :composers, serializer: ComposerSerializer
|
255
|
+
has_one :album, as: :first_release, serializer: AlbumSerializer
|
318
256
|
|
319
|
-
|
320
|
-
|
257
|
+
attributes title: {as: :name}
|
258
|
+
|
259
|
+
# or as a shortcut
|
260
|
+
attributes title: :name
|
321
261
|
end
|
322
262
|
```
|
323
263
|
|
324
|
-
|
264
|
+
### Conditional attributes ❔
|
265
|
+
|
266
|
+
You can render attributes and associations conditionally by using `:if`.
|
325
267
|
|
326
268
|
```ruby
|
327
|
-
class
|
328
|
-
|
329
|
-
def album
|
330
|
-
AlbumSerializer.one(song.album)
|
331
|
-
end
|
269
|
+
class PlayerSerializer < Oj::Serializer
|
270
|
+
attributes :first_name, :last_name, if: -> { player.display_name? }
|
332
271
|
|
333
|
-
|
334
|
-
def composers
|
335
|
-
ComposerSerializer.many(song.composers)
|
336
|
-
end
|
272
|
+
has_one :album, serializer: AlbumSerializer, if: -> { player.album }
|
337
273
|
end
|
338
274
|
```
|
339
275
|
|
340
|
-
|
276
|
+
This is useful in cases where you don't want to `null` values to be in the response.
|
277
|
+
|
278
|
+
## Advanced Usage 🧙♂️
|
341
279
|
|
342
280
|
### Using a different alias for the internal object
|
343
281
|
|
344
|
-
|
282
|
+
In most cases, the default alias for the `object` will be convenient enough.
|
283
|
+
|
284
|
+
However, if you would like to specify it manually, use `object_as`:
|
345
285
|
|
346
286
|
```ruby
|
347
287
|
class DiscographySerializer < Oj::Serializer
|
348
288
|
object_as :artist
|
349
289
|
|
350
290
|
# Now we can use `artist` instead of `object` or `discography`.
|
291
|
+
attribute
|
351
292
|
def latest_albums
|
352
293
|
artist.albums.desc(:year)
|
353
294
|
end
|
354
295
|
end
|
355
296
|
```
|
356
297
|
|
357
|
-
###
|
298
|
+
### Identifier attributes
|
358
299
|
|
359
|
-
|
300
|
+
The `identifier` method allows you to only include an identifier if the record
|
301
|
+
or document has been persisted.
|
360
302
|
|
361
303
|
```ruby
|
362
304
|
class AlbumSerializer < Oj::Serializer
|
363
|
-
|
305
|
+
identifier
|
306
|
+
|
307
|
+
# or if it's a different field
|
308
|
+
identifier :uuid
|
309
|
+
end
|
310
|
+
```
|
311
|
+
|
312
|
+
Additionally, identifier fields are always rendered first, even when sorting
|
313
|
+
fields alphabetically.
|
314
|
+
|
315
|
+
### Transforming attribute keys 🗝
|
316
|
+
|
317
|
+
When serialized data will be consumed from a client language that has different
|
318
|
+
naming conventions, it can be convenient to transform keys accordingly.
|
319
|
+
|
320
|
+
For example, when rendering an API to be consumed from the browser via JavaScript,
|
321
|
+
where properties are traditionally named using camel case.
|
322
|
+
|
323
|
+
Use `transform_keys` to handle that conversion.
|
324
|
+
|
325
|
+
```ruby
|
326
|
+
class BaseSerializer < Oj::Serializer
|
327
|
+
transform_keys :camelize
|
328
|
+
|
329
|
+
# shortcut for
|
330
|
+
transform_keys -> (key) { key.to_s.camelize(:lower) }
|
331
|
+
end
|
332
|
+
```
|
333
|
+
|
334
|
+
This has no performance impact, as keys will be transformed at load time.
|
335
|
+
|
336
|
+
### Sorting attributes 📶
|
364
337
|
|
365
|
-
|
338
|
+
By default attributes are rendered in the order they are defined.
|
366
339
|
|
367
|
-
|
368
|
-
|
369
|
-
|
340
|
+
If you would like to sort attributes alphabetically, you can specify it at a
|
341
|
+
serializer level:
|
342
|
+
|
343
|
+
```ruby
|
344
|
+
class BaseSerializer < Oj::Serializer
|
345
|
+
sort_attributes_by :name # or a Proc
|
346
|
+
end
|
347
|
+
```
|
348
|
+
|
349
|
+
This has no performance impact, as attributes will be sorted at load time.
|
350
|
+
|
351
|
+
### Path helpers 🛣
|
352
|
+
|
353
|
+
In case you need to access path helpers in your serializers, you can use the
|
354
|
+
following:
|
355
|
+
|
356
|
+
```ruby
|
357
|
+
class BaseSerializer < Oj::Serializer
|
358
|
+
include Rails.application.routes.url_helpers
|
359
|
+
|
360
|
+
def default_url_options
|
361
|
+
Rails.application.routes.default_url_options
|
370
362
|
end
|
371
363
|
end
|
372
364
|
```
|
373
365
|
|
374
|
-
|
366
|
+
One slight variation that might make it easier to maintain in the long term is
|
367
|
+
to use a separate singleton service to provide the url helpers and options, and
|
368
|
+
make it available as `urls`.
|
375
369
|
|
376
|
-
|
370
|
+
### Memoization & local state
|
371
|
+
|
372
|
+
Serializers are designed to be stateless so that an instanced can be reused, but
|
373
|
+
sometimes it's convenient to store intermediate calculations.
|
377
374
|
|
378
375
|
Use `memo` for memoization and storing temporary information.
|
379
376
|
|
@@ -381,7 +378,7 @@ Use `memo` for memoization and storing temporary information.
|
|
381
378
|
class DownloadSerializer < Oj::Serializer
|
382
379
|
attributes :filename, :size
|
383
380
|
|
384
|
-
attribute
|
381
|
+
attribute
|
385
382
|
def progress
|
386
383
|
"#{ last_event&.progress || 0 }%"
|
387
384
|
end
|
@@ -396,9 +393,50 @@ private
|
|
396
393
|
end
|
397
394
|
```
|
398
395
|
|
396
|
+
### `hash_attributes` 🚀
|
397
|
+
|
398
|
+
Very convenient when serializing Hash-like structures, this strategy uses the `[]` operator.
|
399
|
+
|
400
|
+
```ruby
|
401
|
+
class PersonSerializer < Oj::Serializer
|
402
|
+
hash_attributes 'first_name', :last_name
|
403
|
+
end
|
404
|
+
|
405
|
+
PersonSerializer.one('first_name' => 'Mary', :middle_name => 'Jane', :last_name => 'Watson')
|
406
|
+
# {first_name: "Mary", last_name: "Watson"}
|
407
|
+
```
|
408
|
+
|
409
|
+
### `mongo_attributes` 🚀
|
410
|
+
|
411
|
+
Reads data directly from `attributes` in a [Mongoid] document.
|
412
|
+
|
413
|
+
By skipping type casting, coercion, and defaults, it [achieves the best performance][raw_benchmarks].
|
414
|
+
|
415
|
+
Although there are some downsides, depending on how consistent your schema is,
|
416
|
+
and which kind of consumer the API has, it can be really powerful.
|
417
|
+
|
418
|
+
```ruby
|
419
|
+
class AlbumSerializer < Oj::Serializer
|
420
|
+
mongo_attributes :id, :name
|
421
|
+
end
|
422
|
+
```
|
423
|
+
|
399
424
|
### Caching 📦
|
400
425
|
|
401
|
-
|
426
|
+
Usually rendering is so fast that __turning caching on can be slower__.
|
427
|
+
|
428
|
+
However, in cases of deeply nested structures, unpredictable query patterns, or
|
429
|
+
methods that take a long time to run, caching can improve performance.
|
430
|
+
|
431
|
+
To enable caching, use `cached`, which calls `cache_key` in the object:
|
432
|
+
|
433
|
+
```ruby
|
434
|
+
class CachedUserSerializer < UserSerializer
|
435
|
+
cached
|
436
|
+
end
|
437
|
+
```
|
438
|
+
|
439
|
+
You can also provide a lambda to `cached_with_key` to define a custom key:
|
402
440
|
|
403
441
|
```ruby
|
404
442
|
class CachedUserSerializer < UserSerializer
|
@@ -408,33 +446,164 @@ class CachedUserSerializer < UserSerializer
|
|
408
446
|
end
|
409
447
|
```
|
410
448
|
|
411
|
-
It will leverage `fetch_multi` when serializing a collection with `many` or
|
449
|
+
It will leverage `fetch_multi` when serializing a collection with `many` or
|
450
|
+
`has_many`, to minimize the amount of round trips needed to read and write all
|
451
|
+
items to cache.
|
452
|
+
|
453
|
+
This works specially well if your cache store also supports `write_multi`.
|
454
|
+
|
455
|
+
### Writing to JSON
|
456
|
+
|
457
|
+
In some corner cases it might be faster to serialize using a `Oj::StringWriter`,
|
458
|
+
which you can access by using `one_as_json` and `many_as_json`.
|
459
|
+
|
460
|
+
Alternatively, you can toggle this mode at a serializer level by using
|
461
|
+
`default_format :json`, or configure it globally from your base serializer:
|
462
|
+
|
463
|
+
```ruby
|
464
|
+
class BaseSerializer < Oj::Serializer
|
465
|
+
default_format :json
|
466
|
+
end
|
467
|
+
```
|
468
|
+
|
469
|
+
This will change the default shortcuts (`render`, `one`, `one_if`, and `many`),
|
470
|
+
so that the serializer writes directly to JSON instead of returning a Hash.
|
471
|
+
|
472
|
+
> **Note**
|
473
|
+
>
|
474
|
+
> This was the default behavior in `oj_serializers` v1, but was replaced with
|
475
|
+
`default_format :hash` in v2.
|
476
|
+
|
477
|
+
<details>
|
478
|
+
<summary>Example Output</summary>
|
479
|
+
|
480
|
+
```json
|
481
|
+
{
|
482
|
+
"name": "Abraxas",
|
483
|
+
"genres": [
|
484
|
+
"Pyschodelic Rock",
|
485
|
+
"Blues Rock",
|
486
|
+
"Jazz Fusion",
|
487
|
+
"Latin Rock"
|
488
|
+
],
|
489
|
+
"release": "September 23, 1970",
|
490
|
+
"songs": [
|
491
|
+
{
|
492
|
+
"track": 1,
|
493
|
+
"name": "Sing Winds, Crying Beasts",
|
494
|
+
"composers": [
|
495
|
+
"Michael Carabello"
|
496
|
+
]
|
497
|
+
},
|
498
|
+
{
|
499
|
+
"track": 2,
|
500
|
+
"name": "Black Magic Woman / Gypsy Queen",
|
501
|
+
"composers": [
|
502
|
+
"Peter Green",
|
503
|
+
"Gábor Szabó"
|
504
|
+
]
|
505
|
+
},
|
506
|
+
{
|
507
|
+
"track": 3,
|
508
|
+
"name": "Oye como va",
|
509
|
+
"composers": [
|
510
|
+
"Tito Puente"
|
511
|
+
]
|
512
|
+
},
|
513
|
+
{
|
514
|
+
"track": 4,
|
515
|
+
"name": "Incident at Neshabur",
|
516
|
+
"composers": [
|
517
|
+
"Alberto Gianquinto",
|
518
|
+
"Carlos Santana"
|
519
|
+
]
|
520
|
+
},
|
521
|
+
{
|
522
|
+
"track": 5,
|
523
|
+
"name": "Se acabó",
|
524
|
+
"composers": [
|
525
|
+
"José Areas"
|
526
|
+
]
|
527
|
+
},
|
528
|
+
{
|
529
|
+
"track": 6,
|
530
|
+
"name": "Mother's Daughter",
|
531
|
+
"composers": [
|
532
|
+
"Gregg Rolie"
|
533
|
+
]
|
534
|
+
},
|
535
|
+
{
|
536
|
+
"track": 7,
|
537
|
+
"name": "Samba pa ti",
|
538
|
+
"composers": [
|
539
|
+
"Santana"
|
540
|
+
]
|
541
|
+
},
|
542
|
+
{
|
543
|
+
"track": 8,
|
544
|
+
"name": "Hope You're Feeling Better",
|
545
|
+
"composers": [
|
546
|
+
"Rolie"
|
547
|
+
]
|
548
|
+
},
|
549
|
+
{
|
550
|
+
"track": 9,
|
551
|
+
"name": "El Nicoya",
|
552
|
+
"composers": [
|
553
|
+
"Areas"
|
554
|
+
]
|
555
|
+
}
|
556
|
+
]
|
557
|
+
}
|
558
|
+
```
|
559
|
+
</details>
|
412
560
|
|
413
|
-
|
561
|
+
Even when using this mode, you can still use rendered values inside arrays,
|
562
|
+
hashes, and other serializers, thanks to [the `raw_json` extensions][raw_json].
|
414
563
|
|
415
564
|
## Design 📐
|
416
565
|
|
417
566
|
Unlike `ActiveModel::Serializer`, which builds a Hash that then gets encoded to
|
418
|
-
JSON, this implementation
|
567
|
+
JSON, this implementation can use `Oj::StringWriter` to write JSON directly,
|
419
568
|
greatly reducing the overhead of allocating and garbage collecting the hashes.
|
420
569
|
|
421
570
|
It also allocates a single instance per serializer class, which makes it easy
|
422
571
|
to use, while keeping memory usage under control.
|
423
572
|
|
573
|
+
The internal design is simple and extensible, and because the library is written
|
574
|
+
in Ruby, creating new serialization strategies requires very little code.
|
575
|
+
Please open a [Discussion] if you need help 😃
|
576
|
+
|
424
577
|
### Comparison with other libraries
|
425
578
|
|
426
579
|
`ActiveModel::Serializer` instantiates one serializer object per item to be serialized.
|
427
580
|
|
428
|
-
Other libraries such as [`jsonapi-serializer`][jsonapi]
|
429
|
-
a `class` instead of an `instance` of a class.
|
430
|
-
|
431
|
-
|
581
|
+
Other libraries such as [`blueprinter`][blueprinter] [`jsonapi-serializer`][jsonapi]
|
582
|
+
evaluate serializers in the context of a `class` instead of an `instance` of a class.
|
583
|
+
The downside is that you can't use instance methods or local memoization, and any
|
584
|
+
mixins must be applied to the class itself.
|
432
585
|
|
433
586
|
[`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.
|
434
587
|
|
435
588
|
`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.
|
436
589
|
|
437
|
-
|
590
|
+
Follow [this discussion][raw_json] to find out more about [the `raw_json` extensions][raw_json] that made this high level of interoperability possible.
|
591
|
+
|
592
|
+
As a result, migrating from `active_model_serializers` is relatively
|
593
|
+
straightforward because instance methods, inheritance, and mixins work as usual.
|
594
|
+
|
595
|
+
### Benchmarks 📊
|
596
|
+
|
597
|
+
This library includes some [benchmarks] to compare performance with similar libraries.
|
598
|
+
|
599
|
+
See [this pull request](https://github.com/ElMassimo/oj_serializers/pull/9) for a quick comparison,
|
600
|
+
or check the CI to see the latest results.
|
601
|
+
|
602
|
+
### Migrating from other libraries
|
603
|
+
|
604
|
+
Please refer to the [migration guide] for a full discussion of the compatibility
|
605
|
+
modes available to make it easier to migrate from `active_model_serializers` and
|
606
|
+
similar libraries.
|
438
607
|
|
439
608
|
## Formatting 📏
|
440
609
|
|