oj_serializers 1.0.2 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|