oj_serializers 1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 807a53bcbe017c0279d9be8f654044a0c8834d289dd2e34646121833923ab4c1
4
+ data.tar.gz: b5f77a958e54273c9729e42a8d9270d4859f1c3aaa338efcbdff0357b3bdc8a5
5
+ SHA512:
6
+ metadata.gz: 0753030fff469265ce72aecab4b8f7289d626bc15355f847dd0c57dccda3c3abbaa269e4154017bf12c9e1e3f0335df02bf62e31faf105f4cd2bd2a40c34b4ea
7
+ data.tar.gz: ad791006f8d82f9a1bba0ce8179d414372d2581fd2181920e9362fdad77b0e707f271a8c9a259e115deedcea9a576dca434b2332aef060502b8b530c69f25c3d
@@ -0,0 +1,3 @@
1
+ ## Oj Serializers 1.0.0 (2020-11-05) ##
2
+
3
+ * Initial Release.
@@ -0,0 +1,465 @@
1
+ <h1 align="center">
2
+ Oj Serializers
3
+ <p align="center">
4
+ <!-- <a href="https://travis-ci.org/ElMassimo/oj_serializers"><img alt="Build Status" src="https://travis-ci.org/ElMassimo/oj_serializers.svg"/></a>
5
+ <a href="http://inch-ci.org/github/ElMassimo/oj_serializers"><img alt="Inline docs" src="http://inch-ci.org/github/ElMassimo/oj_serializers.svg"/></a>
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
+ <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
+ <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/master/LICENSE.txt"><img alt="License" src="https://img.shields.io/badge/license-MIT-428F7E.svg"/></a>
10
+ </p>
11
+ </h1>
12
+
13
+ JSON serializers for Ruby, built on top of the powerful [`oj`][oj] library.
14
+
15
+ [oj]: https://github.com/ohler55/oj
16
+ [mongoid]: https://github.com/mongodb/mongoid
17
+ [ams]: https://github.com/rails-api/active_model_serializers
18
+ [jsonapi]: https://github.com/jsonapi-serializer/jsonapi-serializer
19
+ [panko]: https://github.com/panko-serializer/panko_serializer
20
+ [benchmarks]: https://github.com/ElMassimo/oj_serializers/tree/master/benchmarks
21
+ [raw_benchmarks]: https://github.com/ElMassimo/oj_serializers/blob/master/benchmarks/document_benchmark.rb
22
+ [migration guide]: https://github.com/ElMassimo/oj_serializers/blob/master/MIGRATION_GUIDE.md
23
+ [design]: https://github.com/ElMassimo/oj_serializers#design-
24
+ [raw_json]: https://github.com/ohler55/oj/issues/542
25
+ [trailing_commas]: https://maximomussini.com/posts/trailing-commas/
26
+
27
+ ## Why? 🤔
28
+
29
+ [`ActiveModel::Serializer`][ams] has a nice DSL, but it allocates many objects leading
30
+ to memory bloat, time spent on GC, and lower performance.
31
+
32
+ `Oj::Serializer` provides a similar API, with [better performance][benchmarks].
33
+
34
+ Learn more about [how this library achieves its performance][design].
35
+
36
+ ## Features ⚡️
37
+
38
+ - Declaration syntax similar to Active Model Serializers
39
+ - Reduced memory allocation and [improved performance][benchmarks]
40
+ - Support for `has_one` and `has_many`, compose with `flat_one`
41
+ - Useful development checks to avoid typos and mistakes
42
+ - Integrates nicely with Rails controllers
43
+ - Caching
44
+
45
+ ## Installation 💿
46
+
47
+ Add this line to your application's Gemfile:
48
+
49
+ ```ruby
50
+ gem 'oj_serializers'
51
+ ```
52
+
53
+ And then run:
54
+
55
+ $ bundle install
56
+
57
+ ## Usage 🚀
58
+
59
+ You can define a serializer by subclassing `Oj::Serializer`, and specify which
60
+ attributes should be serialized to JSON.
61
+
62
+ ```ruby
63
+ class AlbumSerializer < Oj::Serializer
64
+ attributes :name, :genres
65
+
66
+ attribute \
67
+ def release
68
+ album.release_date.strftime('%B %d, %Y')
69
+ end
70
+
71
+ has_many :songs, serializer: SongSerializer
72
+ end
73
+ ```
74
+
75
+ <details>
76
+ <summary>Example Output</summary>
77
+
78
+ ```json
79
+ {
80
+ "name": "Abraxas",
81
+ "genres": [
82
+ "Pyschodelic Rock",
83
+ "Blues Rock",
84
+ "Jazz Fusion",
85
+ "Latin Rock"
86
+ ],
87
+ "release": "September 23, 1970",
88
+ "songs": [
89
+ {
90
+ "track": 1,
91
+ "name": "Sing Winds, Crying Beasts",
92
+ "composers": [
93
+ "Michael Carabello"
94
+ ]
95
+ },
96
+ {
97
+ "track": 2,
98
+ "name": "Black Magic Woman / Gypsy Queen",
99
+ "composers": [
100
+ "Peter Green",
101
+ "Gábor Szabó"
102
+ ]
103
+ },
104
+ {
105
+ "track": 3,
106
+ "name": "Oye como va",
107
+ "composers": [
108
+ "Tito Puente"
109
+ ]
110
+ },
111
+ {
112
+ "track": 4,
113
+ "name": "Incident at Neshabur",
114
+ "composers": [
115
+ "Alberto Gianquinto",
116
+ "Carlos Santana"
117
+ ]
118
+ },
119
+ {
120
+ "track": 5,
121
+ "name": "Se acabó",
122
+ "composers": [
123
+ "José Areas"
124
+ ]
125
+ },
126
+ {
127
+ "track": 6,
128
+ "name": "Mother's Daughter",
129
+ "composers": [
130
+ "Gregg Rolie"
131
+ ]
132
+ },
133
+ {
134
+ "track": 7,
135
+ "name": "Samba pa ti",
136
+ "composers": [
137
+ "Santana"
138
+ ]
139
+ },
140
+ {
141
+ "track": 8,
142
+ "name": "Hope You're Feeling Better",
143
+ "composers": [
144
+ "Rolie"
145
+ ]
146
+ },
147
+ {
148
+ "track": 9,
149
+ "name": "El Nicoya",
150
+ "composers": [
151
+ "Areas"
152
+ ]
153
+ }
154
+ ]
155
+ }
156
+ ```
157
+ </details>
158
+
159
+ <br/>
160
+
161
+ To use the serializer, the recommended approach is:
162
+
163
+ ```ruby
164
+ class AlbumsController < ApplicationController
165
+ def show
166
+ album = Album.find(params[:id])
167
+ render json: AlbumSerializer.one(album)
168
+ end
169
+
170
+ def index
171
+ albums = Album.all
172
+ render json: { albums: AlbumSerializer.many(albums) }
173
+ end
174
+ end
175
+ ```
176
+
177
+ If you are using Rails you can also use something closer to Active Model Serializers by adding `sugar`:
178
+
179
+ ```ruby
180
+ require 'oj_serializers/sugar'
181
+
182
+ class AlbumsController < ApplicationController
183
+ def show
184
+ album = Album.find(params[:id])
185
+ render json: album, serializer: AlbumSerializer
186
+ end
187
+
188
+ def index
189
+ albums = Album.all
190
+ render json: albums, each_serializer: AlbumSerializer, root: :albums
191
+ end
192
+ end
193
+ ```
194
+
195
+ It's recommended to create your own `BaseSerializer` class in order to easily
196
+ add custom extensions, specially when migrating from `active_model_serializers`.
197
+
198
+ ## Render DSL 🛠
199
+
200
+ In order to efficiently reuse the instances, serializers can't be instantiated directly. Use `one` and `many` to serialize objects or enumerables:
201
+
202
+ ```ruby
203
+ render json: {
204
+ favorite_album: AlbumSerializer.one(album),
205
+ purchased_albums: AlbumSerializer.many(albums),
206
+ }
207
+ ```
208
+
209
+ You can use these serializers inside arrays, hashes, or even inside `ActiveModel::Serializer` by using a method in the serializer.
210
+
211
+ Follow [this discussion][raw_json] to find out more about [the `raw_json` extensions][raw_json] that made this high level of interoperability possible.
212
+
213
+ ## Attributes DSL 🛠
214
+
215
+ Attributes methods can be used to define which model attributes should be serialized
216
+ to JSON. Each method provides a different strategy to obtain the values to serialize.
217
+
218
+ The internal design is simple and extensible, so creating new strategies requires very little code.
219
+ Please open an issue if you need help 😃
220
+
221
+ ### `attributes`
222
+
223
+ Obtains the attribute value by calling a method in the object being serialized.
224
+
225
+ ```ruby
226
+ class PlayerSerializer < Oj::Serializer
227
+ attributes :full_name
228
+ end
229
+ ```
230
+
231
+ Have in mind that unlike Active Model Serializers, it will _not_ take into
232
+ account methods defined in the serializer. Being explicit about where the
233
+ attribute is coming from makes the serializers easier to understand and more
234
+ maintainable.
235
+
236
+ ### `serializer_attributes`
237
+
238
+ Obtains the attribute value by calling a method defined in the serializer.
239
+
240
+
241
+ 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:
242
+
243
+ ```ruby
244
+ class PlayerSerializer < Oj::Serializer
245
+ attribute \
246
+ def full_name
247
+ "#{player.first_name} #{player.last_name}"
248
+ end
249
+ end
250
+ ```
251
+
252
+ Instance methods can access the object by the serializer name without the
253
+ `Serializer` suffix, `player` in the example above, or directly as `@object`.
254
+
255
+ You can customize this by using [`object_as`](https://github.com/ElMassimo/oj_serializers#using-a-different-alias-for-the-internal-object).
256
+
257
+ ### `ams_attributes` 🐌
258
+
259
+ Works like `attributes` in Active Model Serializers, by calling a method in the serializer if defined, or calling `read_attribute_for_serialization` in the model.
260
+
261
+ ```ruby
262
+ class AlbumSerializer < Oj::Serializer
263
+ ams_attributes :name, :release
264
+
265
+ def release
266
+ album.release_date.strftime('%B %d, %Y')
267
+ end
268
+ end
269
+ ```
270
+
271
+ Should only be used when migrating from Active Model Serializers, as it's slower and can create confusion.
272
+
273
+ Instead, use `attributes` for model methods, and the inline `attribute` for serializer attributes. Being explicit makes serializers easier to understand, and to maintain.
274
+
275
+ Please refer to the [migration guide] for more information.
276
+
277
+ ### `hash_attributes` 🚀
278
+
279
+ Very convenient when serializing Hash-like structures, this strategy uses the `[]` operator.
280
+
281
+ ```ruby
282
+ class PersonSerializer < Oj::Serializer
283
+ hash_attributes 'first_name', :last_name
284
+ end
285
+
286
+ PersonSerializer.one('first_name' => 'Mary', :middle_name => 'Jane', :last_name => 'Watson')
287
+ # {"first_name":"Mary","last_name":"Watson"}
288
+ ```
289
+
290
+ ### `mongo_attributes` 🚀
291
+
292
+ Reads data directly from `attributes` in a [Mongoid] document.
293
+
294
+ By skipping type casting, coercion, and defaults, it [achieves the best performance][raw_benchmarks].
295
+
296
+ Although there are some downsides, depending on how consistent your schema is,
297
+ and which kind of consumer the API has, it can be really powerful.
298
+
299
+ ```ruby
300
+ class AlbumSerializer < Oj::Serializer
301
+ mongo_attributes :id, :name
302
+ end
303
+ ```
304
+
305
+ ## Associations DSL 🛠
306
+
307
+ Use `has_one` to serialize individual objects, and `has_many` to serialize a collection.
308
+
309
+ The value for the association is obtained from a serializer method if defined, or by calling the method in the object being serialized.
310
+
311
+ You must specificy which serializer to use with the `serializer` option.
312
+
313
+ ```ruby
314
+ class SongSerializer < Oj::Serializer
315
+ has_one :album, serializer: AlbumSerializer
316
+ has_many :composers, serializer: ComposerSerializer
317
+
318
+ # You can also compose serializers using `flat_one`.
319
+ flat_one :song, serializer: SongMetadataSerializer
320
+ end
321
+ ```
322
+
323
+ The associations DSL is more concise and achieves better performance, so prefer to use it instead of manually definining attributes:
324
+
325
+ ```ruby
326
+ class SongSerializer < SongMetadataSerializer
327
+ attribute \
328
+ def album
329
+ AlbumSerializer.one(song.album)
330
+ end
331
+
332
+ attribute \
333
+ def composers
334
+ ComposerSerializer.many(song.composers)
335
+ end
336
+ end
337
+ ```
338
+
339
+ ## Other DSL 🛠
340
+
341
+ ### Using a different alias for the internal object
342
+
343
+ You can use `object_as` to create an alias for the serialized object to access it from instance methods:
344
+
345
+ ```ruby
346
+ class DiscographySerializer < Oj::Serializer
347
+ object_as :artist
348
+
349
+ # Now we can use `artist` instead of `object` or `discography`.
350
+ def latest_albums
351
+ artist.albums.desc(:year)
352
+ end
353
+ end
354
+ ```
355
+
356
+ ### Rendering an attribute conditionally
357
+
358
+ All the attributes and association methods can take an `if` option to render conditionally.
359
+
360
+ ```ruby
361
+ class AlbumSerializer < Oj::Serializer
362
+ mongo_attributes :release_date, if: -> { album.released? }
363
+
364
+ has_many :songs, serializer: SongSerializer, if: -> { album.songs.any? }
365
+
366
+ # You can achieve the same by manually defining a method:
367
+ def include_songs?
368
+ album.songs.any?
369
+ end
370
+ end
371
+ ```
372
+
373
+ ### Memoization & Local State
374
+
375
+ Serializers are designed to be stateless so that an instanced can be reused, but sometimes it's convenient to store intermediate calculations.
376
+
377
+ Use `memo` for memoization and storing temporary information.
378
+
379
+ ```ruby
380
+ class DownloadSerializer < Oj::Serializer
381
+ attributes :filename, :size
382
+
383
+ attribute \
384
+ def progress
385
+ "#{ last_event&.progress || 0 }%"
386
+ end
387
+
388
+ private
389
+
390
+ def last_event
391
+ memo.fetch(:last_event) {
392
+ download.events.desc(:created_at).first
393
+ }
394
+ end
395
+ end
396
+ ```
397
+
398
+ ### Caching 📦
399
+
400
+ Use `cached` to leverage key-based caching, which calls `cache_key` in the object. You can also provide a lambda to `cached_with_key` to define a custom key:
401
+
402
+ ```ruby
403
+ class CachedUserSerializer < UserSerializer
404
+ cached_with_key ->(user) {
405
+ "#{ user.id }/#{ user.current_sign_in_at }"
406
+ }
407
+ end
408
+ ```
409
+
410
+ It will leverage `fetch_multi` when serializing a collection with `many` or `has_many`, to minimize the amount of round trips needed to read and write all items to cache. This works specially well if your cache store also supports `write_multi`.
411
+
412
+ Usually serialization happens so fast that __turning caching on can be slower__. Always benchmark to make sure it's worth it, and use caching only for time-consuming or deeply nested structures.
413
+
414
+ ## Design 📐
415
+
416
+ Unlike `ActiveModel::Serializer`, which builds a Hash that then gets encoded to
417
+ JSON, this implementation uses `Oj::StringWriter` to write JSON directly,
418
+ greatly reducing the overhead of allocating and garbage collecting the hashes.
419
+
420
+ It also allocates a single instance per serializer class, which makes it easy
421
+ to use, while keeping memory usage under control.
422
+
423
+ ### Comparison with other libraries
424
+
425
+ `ActiveModel::Serializer` instantiates one serializer object per item to be serialized.
426
+
427
+ Other libraries such as [`jsonapi-serializer`][jsonapi] evaluate serializers in the context of
428
+ a `class` instead of an `instance` of a class. Although it is efficient in terms
429
+ of memory usage, the downside is that you can't use instance methods or local
430
+ memoization, and any mixins must be applied to the class itself.
431
+
432
+ [`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.
433
+
434
+ `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.
435
+
436
+ As a result, migrating from `active_model_serializers` is relatively straightforward because instance methods, inheritance, and mixins work as usual.
437
+
438
+ ## Formatting 📏
439
+
440
+ 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].
441
+
442
+ ```ruby
443
+ class AlbumSerializer < Oj::Serializer
444
+ attributes(
445
+ :genres,
446
+ :name,
447
+ :release_date,
448
+ )
449
+ end
450
+ ```
451
+
452
+ 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`.
453
+
454
+ ## Special Thanks 🙏
455
+
456
+ This library wouldn't be possible without the wonderful and performant [`oj`](https://github.com/ohler55/oj) library. Thanks [Peter](https://github.com/ohler55)! 😃
457
+
458
+ Also, thanks to the libraries that inspired this one:
459
+
460
+ - [`active_model_serializers`][ams]: For the DSL
461
+ - [`panko-serializer`][panko]: For validating that using `Oj::StringWriter` was indeed fast
462
+
463
+ ## License
464
+
465
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'oj'
4
+ require 'oj_serializers/version'
5
+ require 'oj_serializers/setup'
6
+ require 'oj_serializers/serializer'
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_model_serializers'
4
+
5
+ # Extensions: To ensure JsonStringEncoder can process ActiveModel::Serializer
6
+ # as well.
7
+ class ActiveModel::Serializer
8
+ # JsonStringEncoder: Used internally to write a single object to JSON.
9
+ def self.write_one(writer, object, options)
10
+ writer.push_value(new(object, options))
11
+ end
12
+
13
+ # JsonStringEncoder: Used internally to write an array of objects to JSON.
14
+ def self.write_many(writer, array, options)
15
+ writer.push_array
16
+ array.each do |object|
17
+ write_one(writer, object, options)
18
+ end
19
+ writer.pop
20
+ end
21
+
22
+ # JsonStringEncoder: Used internally to instantiate an Oj::StringWriter.
23
+ def self.new_json_writer
24
+ OjSerializers::Serializer.send(:new_json_writer)
25
+ end
26
+ end
27
+
28
+ require 'oj_serializers'
29
+ require 'oj_serializers/sugar'
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'oj_serializers/json_string_encoder'
4
+
5
+ # Internal: Allows to pass Oj serializers as options in `render`.
6
+ module OjSerializers::ControllerSerialization
7
+ extend ActiveSupport::Concern
8
+ include ActionController::Renderers
9
+
10
+ # Internal: Allows to use Oj::Serializer as `serializer` and `each_serializer`
11
+ # as with ActiveModelSerializers.
12
+ #
13
+ # render json: items, each_serializer: ItemSerializer
14
+ # render json: item, serializer: ItemSerializer
15
+ #
16
+ # NOTE: In practice, it should be preferable to simply do:
17
+ #
18
+ # render json: ItemSerializer.many(items)
19
+ # render json: ItemSerializer.one(item)
20
+ #
21
+ # which is more performant.
22
+ %i[_render_option_json _render_with_renderer_json].each do |renderer_method|
23
+ define_method renderer_method do |resource, **options|
24
+ serializer_class = options[:serializer] || options[:each_serializer]
25
+ if serializer_class && serializer_class < OjSerializers::Serializer
26
+ super(OjSerializers::JsonStringEncoder.encode_to_json(resource, options), options.except(:root, :serializer, :each_serializer))
27
+ else
28
+ super(resource, **options)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Public: Contains utility functions to render objects to JSON.
4
+ #
5
+ # Useful to instantiate a single `JsonWriter` when rendering new serializers.
6
+ module OjSerializers::JsonStringEncoder
7
+ class << self
8
+ # Public: Allows to use Oj::Serializer in `serializer` and `each_serializer`
9
+ # as with ActiveModelSerializers.
10
+ # render json: items, each_serializer: ItemSerializer
11
+ # render json: item, serializer: ItemSerializer
12
+ #
13
+ # Returns a JSON string.
14
+ #
15
+ # NOTE: Unlike the default encoder, this one will use the `root` option
16
+ # regardless of whether a serializer is specified or not.
17
+ def encode_to_json(object, root: nil, serializer: nil, each_serializer: nil, **extras)
18
+ # NOTE: Serializers may override `new_json_writer` to modify the behavior.
19
+ writer = (serializer || each_serializer || OjSerializers::Serializer).send(:new_json_writer)
20
+
21
+ if root
22
+ writer.push_object
23
+ writer.push_key(root.to_s)
24
+ end
25
+
26
+ if serializer
27
+ serializer.write_one(writer, object, extras)
28
+ elsif each_serializer
29
+ each_serializer.write_many(writer, object, extras)
30
+ elsif object.is_a?(String)
31
+ return object unless root
32
+
33
+ writer.push_json(object)
34
+ else
35
+ writer.push_value(object)
36
+ end
37
+
38
+ writer.pop if root
39
+
40
+ writer.to_json
41
+ end
42
+
43
+ # Allows to detect misusage of the options during development.
44
+ if OjSerializers::Serializer::DEV_MODE
45
+ alias actual_encode_to_json encode_to_json
46
+ def encode_to_json(object, root: nil, serializer: nil, each_serializer: nil, **extras)
47
+ if serializer && serializer < OjSerializers::Serializer
48
+ raise ArgumentError, 'You must use `each_serializer` when serializing collections' if object.respond_to?(:each)
49
+ end
50
+ if each_serializer && each_serializer < OjSerializers::Serializer
51
+ raise ArgumentError, 'You must use `serializer` when serializing a single object' unless object.respond_to?(:each)
52
+ end
53
+ actual_encode_to_json(object, root: root, serializer: serializer, each_serializer: each_serializer, **extras)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Public: Allows to prevent double encoding an existing JSON string.
4
+ #
5
+ # NOTE: Oj's raw_json option means there's no performance overhead, as it would
6
+ # occur with the previous alternative of parsing the JSON string.
7
+ class OjSerializers::JsonValue
8
+ # Helper: Expects an Array of JSON-encoded strings and wraps them in a JSON array.
9
+ def self.array(json_rows)
10
+ new("[#{json_rows.join(',')}]")
11
+ end
12
+
13
+ def initialize(json)
14
+ @json = json
15
+ end
16
+
17
+ # Public: Return the internal json when using string interpolation.
18
+ def to_s
19
+ @json
20
+ end
21
+
22
+ # Internal: Used by Oj::Rails::Encoder because we use the `raw_json` option.
23
+ def raw_json(*)
24
+ @json
25
+ end
26
+
27
+ # Internal: Used by Oj::Rails::Encoder when found inside a Hash or Array.
28
+ def as_json(_options = nil)
29
+ self
30
+ end
31
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Internal: Provides a simple API on top of Hash for memoization purposes.
4
+ class OjSerializers::Memo
5
+ def initialize
6
+ @cache = {}
7
+ end
8
+
9
+ def clear
10
+ @cache.clear
11
+ end
12
+
13
+ def fetch(key)
14
+ @cache.fetch(key) { @cache[key] = yield }
15
+ end
16
+ end
@@ -0,0 +1,465 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/module/delegation'
4
+ require 'active_support/core_ext/object/try'
5
+ require 'active_support/core_ext/string/inflections'
6
+
7
+ require 'oj'
8
+ require 'oj_serializers/memo'
9
+ require 'oj_serializers/json_value'
10
+
11
+ # Public: Implementation of an "ActiveModelSerializer"-like DSL, but with a
12
+ # design that allows replacing the internal object, which greatly reduces object
13
+ # allocation.
14
+ #
15
+ # Unlike ActiveModelSerializer, which builds a Hash which then gets encoded to
16
+ # JSON, this implementation allows to use Oj::StringWriter to write directly to
17
+ # JSON, greatly reducing the overhead of allocating and garbage collecting the
18
+ # hashes.
19
+ class OjSerializers::Serializer
20
+ # Public: Used to validate incorrect memoization during development. Users of
21
+ # this library might add additional options as needed.
22
+ ALLOWED_INSTANCE_VARIABLES = %w[memo object].freeze
23
+
24
+ CACHE = (defined?(Rails) && Rails.cache) ||
25
+ (defined?(ActiveSupport::Cache::MemoryStore) ? ActiveSupport::Cache::MemoryStore.new : OjSerializers::Memo.new)
26
+
27
+ # Internal: The environment the app is currently running on.
28
+ environment = ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'production'
29
+
30
+ # Internal: Used to display warnings or detect misusage during development.
31
+ DEV_MODE = %w[test development].include?(environment) && !ENV['BENCHMARK']
32
+
33
+ DEFAULT_OPTIONS = {}.freeze
34
+
35
+ # Backwards Compatibility: Allows to access options passed through `render json`,
36
+ # in the same way than ActiveModel::Serializers.
37
+ def options
38
+ @object.try(:options) || DEFAULT_OPTIONS
39
+ end
40
+
41
+ # Internal: Used internally to write attributes and associations to JSON.
42
+ #
43
+ # NOTE: Binds this instance to the specified object and options and writes
44
+ # to json using the provided writer.
45
+ def write_flat(writer, item)
46
+ @memo.clear if defined?(@memo)
47
+ @object = item
48
+ write_to_json(writer)
49
+ end
50
+
51
+ # NOTE: Helps developers to remember to keep serializers stateless.
52
+ if DEV_MODE
53
+ prepend(Module.new do
54
+ def write_flat(writer, item)
55
+ if instance_values.keys.any? { |key| !ALLOWED_INSTANCE_VARIABLES.include?(key) }
56
+ bad_keys = instance_values.keys.reject { |key| ALLOWED_INSTANCE_VARIABLES.include?(key) }
57
+ raise ArgumentError, "Serializer instances are reused so they must be stateless. Use `memo.fetch` for memoization purposes instead. Bad keys: #{bad_keys.join(',')}"
58
+ end
59
+ super
60
+ end
61
+ end)
62
+ end
63
+
64
+ # Internal: Used internally to write a single object to JSON.
65
+ #
66
+ # writer - writer used to serialize results
67
+ # item - item to serialize results for
68
+ # options - list of external options to pass to the serializer (available as `options`)
69
+ #
70
+ # NOTE: Binds this instance to the specified object and options and writes
71
+ # to json using the provided writer.
72
+ def write_one(writer, item, options = nil)
73
+ item.define_singleton_method(:options) { options } if options
74
+ writer.push_object
75
+ write_flat(writer, item)
76
+ writer.pop
77
+ end
78
+
79
+ # Internal: Used internally to write an array of objects to JSON.
80
+ #
81
+ # writer - writer used to serialize results
82
+ # items - items to serialize results for
83
+ # options - list of external options to pass to the serializer (available as `options`)
84
+ def write_many(writer, items, options = nil)
85
+ writer.push_array
86
+ items.each do |item|
87
+ write_one(writer, item, options)
88
+ end
89
+ writer.pop
90
+ end
91
+
92
+ protected
93
+
94
+ # Internal: An internal cache that can be used for temporary memoization.
95
+ def memo
96
+ defined?(@memo) ? @memo : @memo = OjSerializers::Memo.new
97
+ end
98
+
99
+ private
100
+
101
+ # Strategy: Writes an _id value to JSON using `id` as the key instead.
102
+ # NOTE: We skip the id for non-persisted documents, since it doesn't actually
103
+ # identify the document (it will change once it's persisted).
104
+ def write_value_using_id_strategy(writer, _key)
105
+ writer.push_value(@object.attributes['_id'], 'id') unless @object.new_record?
106
+ end
107
+
108
+ # Strategy: Writes an Mongoid attribute to JSON, this is the fastest strategy.
109
+ def write_value_using_mongoid_strategy(writer, key)
110
+ writer.push_value(@object.attributes[key], key)
111
+ end
112
+
113
+ # Strategy: Writes a Hash value to JSON, works with String or Symbol keys.
114
+ def write_value_using_hash_strategy(writer, key)
115
+ writer.push_value(@object[key], key.to_s)
116
+ end
117
+
118
+ # Strategy: Obtains the value by calling a method in the object, and writes it.
119
+ def write_value_using_method_strategy(writer, key)
120
+ writer.push_value(@object.send(key), key)
121
+ end
122
+
123
+ # Strategy: Obtains the value by calling a method in the serializer.
124
+ def write_value_using_serializer_strategy(writer, key)
125
+ writer.push_value(send(key), key)
126
+ end
127
+
128
+ # Override to detect missing attribute errors locally.
129
+ if DEV_MODE
130
+ alias original_write_value_using_method_strategy write_value_using_method_strategy
131
+ def write_value_using_method_strategy(writer, key)
132
+ original_write_value_using_method_strategy(writer, key)
133
+ rescue NoMethodError => e
134
+ raise e, "Perhaps you meant to call #{key.inspect} in #{self.class.name} instead?\nTry using `serializer_attributes :#{key}` or `attribute def #{key}`.\n#{e.message}"
135
+ end
136
+
137
+ alias original_write_value_using_mongoid_strategy write_value_using_mongoid_strategy
138
+ def write_value_using_mongoid_strategy(writer, key)
139
+ original_write_value_using_mongoid_strategy(writer, key).tap do
140
+ # Apply a fake selection when 'only' is not used, so that we allow
141
+ # read_attribute to fail on typos, renamed, and removed fields.
142
+ @object.__selected_fields = @object.fields.merge(@object.relations.select { |_key, value| value.embedded? }).transform_values { 1 } unless @object.__selected_fields
143
+ @object.read_attribute(key) # Raise a missing attribute exception if it's missing.
144
+ end
145
+ rescue StandardError => e
146
+ raise ActiveModel::MissingAttributeError, "#{e.message} in #{self.class} for #{@object.inspect}"
147
+ end
148
+ end
149
+
150
+ class << self
151
+ # Internal: We want to discourage instantiating serializers directly, as it
152
+ # prevents the possibility of reusing an instance.
153
+ #
154
+ # NOTE: `one` serves as a replacement for `new` in these serializers.
155
+ private :new
156
+
157
+ # Internal: Delegates to the instance methods, the advantage is that we can
158
+ # reuse the same serializer instance to serialize different objects.
159
+ delegate :write_one, :write_many, :write_flat, to: :instance
160
+
161
+ # Internal: Keep a reference to the default `write_one` method so that we
162
+ # can use it inside cached overrides and benchmark tests.
163
+ alias non_cached_write_one write_one
164
+
165
+ # Internal: Keep a reference to the default `write_many` method so that we
166
+ # can use it inside cached overrides and benchmark tests.
167
+ alias non_cached_write_many write_many
168
+
169
+ # Helper: Serializes the item unless it's nil.
170
+ def one_if(item, options = nil)
171
+ one(item, options) if item
172
+ end
173
+
174
+ # Public: Serializes the configured attributes for the specified object.
175
+ #
176
+ # item - the item to serialize
177
+ # options - list of external options to pass to the sub class (available in `item.options`)
178
+ #
179
+ # Returns an Oj::StringWriter instance, which is encoded as raw json.
180
+ def one(item, options = nil)
181
+ writer = new_json_writer
182
+ write_one(writer, item, options)
183
+ writer
184
+ end
185
+
186
+ # Public: Serializes an array of items using this serializer.
187
+ #
188
+ # items - Must respond to `each`.
189
+ # options - list of external options to pass to the sub class (available in `item.options`)
190
+ #
191
+ # Returns an Oj::StringWriter instance, which is encoded as raw json.
192
+ def many(items, options = nil)
193
+ writer = new_json_writer
194
+ write_many(writer, items, options)
195
+ writer
196
+ end
197
+
198
+ # Public: Creates an alias for the internal object.
199
+ def object_as(name)
200
+ define_method(name) { @object }
201
+ end
202
+
203
+ # Internal: Will alias the object according to the name of the wrapper class.
204
+ def inherited(subclass)
205
+ object_alias = subclass.name.demodulize.chomp('Serializer').underscore
206
+ subclass.object_as(object_alias) unless method_defined?(object_alias)
207
+ super
208
+ end
209
+
210
+ # Internal: List of attributes to be serialized.
211
+ #
212
+ # Any attributes defined in parent classes are inherited.
213
+ def _attributes
214
+ @_attributes = superclass.try(:_attributes)&.dup || {} unless defined?(@_attributes)
215
+ @_attributes
216
+ end
217
+
218
+ # Internal: List of associations to be serialized.
219
+ # Any associations defined in parent classes are inherited.
220
+ def _associations
221
+ @_associations = superclass.try(:_associations)&.dup || {} unless defined?(@_associations)
222
+ @_associations
223
+ end
224
+
225
+ # Internal: Iterating arrays is faster than iterating hashes.
226
+ attr_reader :_attributes_entries, :_associations_entries
227
+
228
+ protected
229
+
230
+ # Internal: Calculates the cache_key used to cache one serialized item.
231
+ def item_cache_key(item, cache_key_proc)
232
+ ActiveSupport::Cache.expand_cache_key(cache_key_proc.call(item))
233
+ end
234
+
235
+ # Public: Allows to define a cache key strategy for the serializer.
236
+ # Defaults to calling cache_key in the object if no key is provided.
237
+ #
238
+ # NOTE: Benchmark it, sometimes caching is actually SLOWER.
239
+ def cached(cache_key_proc = :cache_key.to_proc)
240
+ cache_options = { namespace: "#{name}#write_to_json", version: OjSerializers::VERSION }.freeze
241
+
242
+ # Internal: Redefine `write_one` to use the cache for the serialized JSON.
243
+ define_singleton_method(:write_one) do |external_writer, item, options = nil|
244
+ cached_item = CACHE.fetch(item_cache_key(item, cache_key_proc), cache_options) do
245
+ writer = new_json_writer
246
+ non_cached_write_one(writer, item, options)
247
+ writer.to_json
248
+ end
249
+ external_writer.push_json("#{cached_item}\n") # Oj.dump expects a new line terminator.
250
+ end
251
+
252
+ # Internal: Redefine `write_many` to use fetch_multi from cache.
253
+ define_singleton_method(:write_many) do |external_writer, items, options = nil|
254
+ # We define a one-off method for the class to receive the entire object
255
+ # inside the `fetch_multi` block. Otherwise we would only get the cache
256
+ # key, and we would need to build a Hash to retrieve the object.
257
+ #
258
+ # NOTE: The assignment is important, as queries would return different
259
+ # objects when expanding with the splat in fetch_multi.
260
+ items = items.entries.each do |item|
261
+ item_key = item_cache_key(item, cache_key_proc)
262
+ item.define_singleton_method(:cache_key) { item_key }
263
+ end
264
+
265
+ # Fetch all items at once by leveraging `read_multi`.
266
+ #
267
+ # NOTE: Memcached does not support `write_multi`, if we switch the cache
268
+ # store to use Redis performance would improve a lot for this case.
269
+ cached_items = CACHE.fetch_multi(*items, cache_options) do |item|
270
+ writer = new_json_writer
271
+ non_cached_write_one(writer, item, options)
272
+ writer.to_json
273
+ end.values
274
+ external_writer.push_json("#{OjSerializers::JsonValue.array(cached_items)}\n") # Oj.dump expects a new line terminator.
275
+ end
276
+ end
277
+ alias cached_with_key cached
278
+
279
+ # Internal: The writer to use to write to json
280
+ def new_json_writer
281
+ Oj::StringWriter.new(mode: :rails)
282
+ end
283
+
284
+ # Public: Specify a collection of objects that should be serialized using
285
+ # the specified serializer.
286
+ def has_many(name, root: name, serializer:, **options)
287
+ add_association(name, write_method: :write_many, root: root, serializer: serializer, **options)
288
+ end
289
+
290
+ # Public: Specify an object that should be serialized using the serializer.
291
+ def has_one(name, root: name, serializer:, **options)
292
+ add_association(name, write_method: :write_one, root: root, serializer: serializer, **options)
293
+ end
294
+
295
+ # Public: Specify an object that should be serialized using the serializer,
296
+ # but unlike `has_one`, this one will write the attributes directly without
297
+ # wrapping it in an object.
298
+ def flat_one(name, root: false, serializer:, **options)
299
+ add_association(name, write_method: :write_flat, root: root, serializer: serializer, **options)
300
+ end
301
+
302
+ # Public: Specify which attributes are going to be obtained from indexing
303
+ # the object.
304
+ def hash_attributes(*method_names, **options)
305
+ options = { **options, strategy: :write_value_using_hash_strategy }
306
+ method_names.each { |name| _attributes[name] = options }
307
+ end
308
+
309
+ # Public: Specify which attributes are going to be obtained from indexing
310
+ # a Mongoid model's `attributes` hash directly, for performance.
311
+ #
312
+ # Automatically renames `_id` to `id` for Mongoid models.
313
+ #
314
+ # See ./benchmarks/document_benchmark.rb
315
+ def mongo_attributes(*method_names, **options)
316
+ add_attribute('id', **options, strategy: :write_value_using_id_strategy) if method_names.delete(:id)
317
+ add_attributes(method_names, **options, strategy: :write_value_using_mongoid_strategy)
318
+ end
319
+
320
+ # Public: Specify which attributes are going to be obtained by calling a
321
+ # method in the object.
322
+ def attributes(*method_names, **options)
323
+ add_attributes(method_names, **options, strategy: :write_value_using_method_strategy)
324
+ end
325
+
326
+ # Public: Specify which attributes are going to be obtained by calling a
327
+ # method in the serializer.
328
+ #
329
+ # NOTE: This can be one of the slowest strategies, when in doubt, measure.
330
+ def serializer_attributes(*method_names, **options)
331
+ add_attributes(method_names, **options, strategy: :write_value_using_serializer_strategy)
332
+ end
333
+
334
+ # Syntax Sugar: Allows to use it before a method name.
335
+ #
336
+ # Example:
337
+ # attribute \
338
+ # def full_name
339
+ # "#{ first_name } #{ last_name }"
340
+ # end
341
+ alias attribute serializer_attributes
342
+
343
+ # Backwards Compatibility: Meant only to replace Active Model Serializers,
344
+ # calling a method in the serializer, or using `read_attribute_for_serialization`.
345
+ #
346
+ # NOTE: Prefer to use `attributes` or `serializer_attributes` explicitly.
347
+ def ams_attributes(*method_names, **options)
348
+ method_names.each do |method_name|
349
+ define_method(method_name) { @object.read_attribute_for_serialization(method_name) } unless method_defined?(method_name)
350
+ end
351
+ add_attributes(method_names, **options, strategy: :write_value_using_serializer_strategy)
352
+ end
353
+
354
+ private
355
+
356
+ def add_attributes(names, options)
357
+ names.each { |name| add_attribute(name, options) }
358
+ end
359
+
360
+ def add_attribute(name, options)
361
+ _attributes[name.to_s.freeze] = options
362
+ end
363
+
364
+ def add_association(name, options)
365
+ _associations[name.to_s.freeze] = options
366
+ end
367
+
368
+ # Internal: We generate code for the serializer to avoid the overhead of
369
+ # using variables for method names, having to iterate the list of attributes
370
+ # and associations, and the overhead of using `send` with dynamic methods.
371
+ #
372
+ # As a result, the performance is the same as writing the most efficient
373
+ # code by hand.
374
+ def write_to_json_body
375
+ <<~WRITE_TO_JSON
376
+ # Public: Writes this serializer content to a provided Oj::StringWriter.
377
+ def write_to_json(writer)
378
+ #{ _attributes.map { |method_name, attribute_options|
379
+ write_conditional_body(method_name, attribute_options) {
380
+ <<-WRITE_ATTRIBUTE
381
+ #{attribute_options.fetch(:strategy)}(writer, #{method_name.inspect})
382
+ WRITE_ATTRIBUTE
383
+ }
384
+ }.join }
385
+ #{ _associations.map { |method_name, association_options|
386
+ write_conditional_body(method_name, association_options) {
387
+ write_association_body(method_name, association_options)
388
+ }
389
+ }.join}
390
+ end
391
+ WRITE_TO_JSON
392
+ end
393
+
394
+ # Internal: Returns the code to render an attribute or association
395
+ # conditionally.
396
+ #
397
+ # NOTE: Detects any include methods defined in the serializer, or defines
398
+ # one by using the lambda passed in the `if` option, if any.
399
+ def write_conditional_body(method_name, options)
400
+ include_method_name = "include_#{method_name}?"
401
+ if render_if = options[:if]
402
+ define_method(include_method_name, &render_if)
403
+ end
404
+
405
+ if method_defined?(include_method_name)
406
+ "if #{include_method_name};#{yield};end\n"
407
+ else
408
+ yield
409
+ end
410
+ end
411
+
412
+ # Internal: Returns the code for the association method.
413
+ def write_association_body(method_name, association_options)
414
+ # Use a serializer method if defined, else call the association in the object.
415
+ association_method = method_defined?(method_name) ? method_name : "@object.#{method_name}"
416
+ association_root = association_options[:root]
417
+ serializer_class = association_options.fetch(:serializer)
418
+
419
+ case write_method = association_options.fetch(:write_method)
420
+ when :write_one
421
+ <<-WRITE_ONE
422
+ if associated_object = #{association_method}
423
+ writer.push_key(#{association_root.to_s.inspect})
424
+ #{serializer_class}.write_one(writer, associated_object)
425
+ end
426
+ WRITE_ONE
427
+ when :write_many
428
+ <<-WRITE_MANY
429
+ writer.push_key(#{association_root.to_s.inspect})
430
+ #{serializer_class}.write_many(writer, #{association_method})
431
+ WRITE_MANY
432
+ when :write_flat
433
+ <<-WRITE_FLAT
434
+ #{serializer_class}.write_flat(writer, #{association_method})
435
+ WRITE_FLAT
436
+ else
437
+ raise ArgumentError, "Unknown write_method #{write_method}"
438
+ end
439
+ end
440
+
441
+ # Internal: Allows to obtain a pre-existing instance and binds it to the
442
+ # specified object.
443
+ #
444
+ # NOTE: Each class is only instantiated once to reduce object allocation.
445
+ # For that reason, serializers must be completely stateless (or use global
446
+ # state).
447
+ def instance
448
+ Thread.current[instance_key] ||= new
449
+ end
450
+
451
+ # Internal: Cache key to set a thread-local instance.
452
+ def instance_key
453
+ unless defined?(@instance_key)
454
+ @instance_key = "#{name.underscore}_instance".to_sym
455
+ # We take advantage of the fact that this method will always be called
456
+ # before instantiating a serializer to define the write_to_json method.
457
+ class_eval(write_to_json_body)
458
+ raise ArgumentError, "You must use `cached ->(object) { ... }` in order to specify a different cache key when subclassing #{name}." if method_defined?(:cache_key) || respond_to?(:cache_key)
459
+ end
460
+ @instance_key
461
+ end
462
+ end
463
+ end
464
+
465
+ Oj::Serializer = OjSerializers::Serializer unless defined?(Oj::Serializer)
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'oj'
4
+
5
+ # NOTE: We automatically set the necessary configuration unless it had been
6
+ # explicitly set beforehand.
7
+ unless Oj.default_options[:use_raw_json]
8
+ require 'rails'
9
+ Oj.optimize_rails
10
+ Oj.default_options = { mode: :rails, use_raw_json: true }
11
+ end
12
+
13
+ # NOTE: Add an optimization to make it easier to work with a StringWriter
14
+ # transparently in different scenarios.
15
+ class Oj::StringWriter
16
+ # Patch: ActiveSupport can pass an options argument to `as_json` when
17
+ # serializing a Hash or Array.
18
+ alias_method :original_as_json, :as_json
19
+ def as_json(_options = nil)
20
+ original_as_json
21
+ end
22
+
23
+ # Optimization: We can use `to_s` directly, this is not important but gives a
24
+ # slight boost to a few use cases that use it for caching in Memcached.
25
+ def to_json(_options = nil)
26
+ to_s.delete_suffix("\n")
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/railtie'
4
+ require 'action_controller'
5
+ require 'action_controller/railtie'
6
+
7
+ require 'oj_serializers'
8
+ require 'oj_serializers/controller_serialization'
9
+
10
+ # Internal: Allows to pass Oj serializers as options in `render`.
11
+ class OjSerializers::Railtie < Rails::Railtie
12
+ initializer 'oj_serializers.action_controller' do
13
+ ActiveSupport.on_load(:action_controller) do
14
+ include(OjSerializers::ControllerSerialization)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OjSerializers
4
+ VERSION = '1.0.0'
5
+ end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: oj_serializers
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Maximo Mussini
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-11-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: oj
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 3.8.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 3.8.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: actionpack
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '4.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '4.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: railties
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '4.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '4.0'
55
+ description: oj_serializers leverages the performance of the oj JSON serialization
56
+ library, and minimizes object allocations, all while provding a similar API to Active
57
+ Model Serializers.
58
+ email:
59
+ - maximomussini@gmail.com
60
+ executables: []
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - CHANGELOG.md
65
+ - README.md
66
+ - lib/oj_serializers.rb
67
+ - lib/oj_serializers/compat.rb
68
+ - lib/oj_serializers/controller_serialization.rb
69
+ - lib/oj_serializers/json_string_encoder.rb
70
+ - lib/oj_serializers/json_value.rb
71
+ - lib/oj_serializers/memo.rb
72
+ - lib/oj_serializers/serializer.rb
73
+ - lib/oj_serializers/setup.rb
74
+ - lib/oj_serializers/sugar.rb
75
+ - lib/oj_serializers/version.rb
76
+ homepage: https://github.com/ElMassimo/oj_serializers
77
+ licenses:
78
+ - MIT
79
+ metadata:
80
+ homepage_uri: https://github.com/ElMassimo/oj_serializers
81
+ source_code_uri: https://github.com/ElMassimo/oj_serializers
82
+ changelog_uri: https://github.com/ElMassimo/oj_serializers/blob/master/CHANGELOG.md
83
+ post_install_message:
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: 2.3.0
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ requirements: []
98
+ rubygems_version: 3.1.2
99
+ signing_key:
100
+ specification_version: 4
101
+ summary: A lighter JSON serializer for Ruby Objects in Rails. Easily migrate away
102
+ from Active Model Serializers.
103
+ test_files: []