oj_serializers 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []