Skip to content

Conversation

@dvandersluis
Copy link

@dvandersluis dvandersluis commented Sep 17, 2020

Port of Netflix/fast_jsonapi#410. Fixes #121.

What is the current behavior?

Collections of mixed type cannot be serialized with a different serializer for each class.

What is the new behavior?

Serializers can be defined for classes by specifying the serializer for each class, for example:

# single record
VehicleSerializer.new(car, serializers: { Car: CarSerializer, Bus: BusSerializer })

# collection
VehicleSerializer.new([car, bus], serializers: { Car: CarSerializer, Bus: BusSerializer })

If a object is attempted to be serialized when it doesn't have a corresponding serializer set, ArgumentError will be raised. If no serializers (or empty) is specified, the receiver class will be used.

From the original PR:

I foresee using this with ApplicationSerializer or the like as a base class (this makes more semantic sense to me). I experimented a bit with adding a generic abstract serializer to use, but I don't know if that's appropriate for this change.

Another way to do this that I like would be to have a static method on ObjectSerializer, such as:

FastJsonapi::ObjectSerializer.serialize(
  [car, bus],
  serializers: { Car: CarSerializer, Bus: BusSerializer }
)

but to do so we would need a base class to instantiate.


(I believe this would still be a good idea, but there still isn't a base class to use.)

This has been in production at Pexels for a year and a half with no noticeable issues.

Checklist

Please make sure the following requirements are complete:

  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been reviewed and added / updated if needed (for bug fixes /
    features)
  • All automated checks pass (CI/CD)

@dvandersluis dvandersluis force-pushed the heterogeneous-collection branch from c53ad46 to ffd7526 Compare September 17, 2020 19:29
Copy link
Collaborator

@stas stas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dvandersluis generally this looks ready to merge. I left some notes since there might be things that changed since the last time this was submitted for the review.

Maybe you could take a look at these, let me know.

Before we'd merge this, I'd like us to set up a performance benchmark to see if there are any serious performance changes. I'd appreciate if you could take some time to look into it:
https://github.com/jsonapi-serializer/comparisons

Thank you for the contribution!

end
end

def serializer_for(record)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Crap, sorry! Missed that (and so did the tests I guess? haha)

RSpec.describe JSONAPI::Serializer do
let(:car) { Car.fake }
let(:bus) { Bus.fake }
let(:truck) { Truck.fake }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to reuse existing serializers. Consider using the Movie, Actor, User for tests.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing.


context 'when serializing a mixed collection' do
it 'uses the correct serializer for each object' do
vehicles = VehicleSerializer.new([car, bus], serializers: { Car: CarSerializer, Bus: BusSerializer }).to_hash
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest, I'm not sure how OK is to always have to pass the serializers inline. I'd rather memoize the serializer_for and cache calls to it...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean here?

When I first wrote this I went back and forth about how to define what serializers to use where but couldn't come up with a better way to do this within the existing class structure. If you have a better idea, by all means!

end
end

context 'when serializing an single object' do
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this test is needed at all.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Serializing collections and single objects use a different code path, hence the separate tests. If you don't think it's useful, feel free to delete.

@stas
Copy link
Collaborator

stas commented Oct 15, 2020

@dvandersluis do you mind if I help a bit to get this merged? I'd like to include it in the next release 🙃

@dvandersluis
Copy link
Author

@stas yeah sorry! I meant to get back to this and didn't get a chance.

@stas
Copy link
Collaborator

stas commented Oct 15, 2020

No worries, I'll run some benchmarks as well first.

@stas
Copy link
Collaborator

stas commented Oct 16, 2020

Comparisons before:

/app # benchmark-driver ./all.yml --bundler
Warming up --------------------------------------
         🐌...JSONAPI::Serializer    82.273k i/s -     85.488k times in 1.039082s (12.15μs/i)
   🐌...JSONAPI::Serializer(many)    133.524 i/s -     140.000 times in 1.048499s (7.49ms/i)
🐌...JSONAPI::Serializer(include)     25.728 i/s -      27.000 times in 1.049427s (38.87ms/i)
   🐌...JSONAPI::Serializer(deep)     20.997 i/s -      21.000 times in 1.000154s (47.63ms/i)
Calculating -------------------------------------
         🐌...JSONAPI::Serializer    81.657k i/s -    246.817k times in 3.022594s (12.25μs/i)
   🐌...JSONAPI::Serializer(many)    131.722 i/s -     400.000 times in 3.036699s (7.59ms/i)
🐌...JSONAPI::Serializer(include)     25.386 i/s -      77.000 times in 3.033172s (39.39ms/i)
   🐌...JSONAPI::Serializer(deep)     24.874 i/s -      62.000 times in 2.492579s (40.20ms/i)

Comparison:
         🐌...JSONAPI::Serializer:     81657.3 i/s 
   🐌...JSONAPI::Serializer(many):       131.7 i/s - 619.92x  slower
🐌...JSONAPI::Serializer(include):        25.4 i/s - 3216.63x  slower
   🐌...JSONAPI::Serializer(deep):        24.9 i/s - 3282.86x  slower

And after:

/app # benchmark-driver ./all.yml --bundler
Warming up --------------------------------------
         🐌...JSONAPI::Serializer    80.680k i/s -     86.316k times in 1.069858s (12.39μs/i)
   🐌...JSONAPI::Serializer(many)    130.701 i/s -     132.000 times in 1.009937s (7.65ms/i)
🐌...JSONAPI::Serializer(include)     25.565 i/s -      27.000 times in 1.056137s (39.12ms/i)
   🐌...JSONAPI::Serializer(deep)     22.297 i/s -      24.000 times in 1.076390s (44.85ms/i)
Calculating -------------------------------------
         🐌...JSONAPI::Serializer    81.154k i/s -    242.039k times in 2.982459s (12.32μs/i)
   🐌...JSONAPI::Serializer(many)    132.644 i/s -     392.000 times in 2.955281s (7.54ms/i)
🐌...JSONAPI::Serializer(include)     25.622 i/s -      76.000 times in 2.966254s (39.03ms/i)
   🐌...JSONAPI::Serializer(deep)     23.679 i/s -      66.000 times in 2.787241s (42.23ms/i)

Comparison:
         🐌...JSONAPI::Serializer:     81154.2 i/s 
   🐌...JSONAPI::Serializer(many):       132.6 i/s - 611.82x  slower
🐌...JSONAPI::Serializer(include):        25.6 i/s - 3167.42x  slower
   🐌...JSONAPI::Serializer(deep):        23.7 i/s - 3427.22x  slower

There's an insignificant penalty here, so I'll move on with getting this ready to be merged (might need to cherry pick just some of your work @dvandersluis).

@stas
Copy link
Collaborator

stas commented Oct 24, 2020

@dvandersluis I'll close this in favor of #141 for now.

@stas stas closed this Oct 24, 2020
@dvandersluis
Copy link
Author

@stas hey 👋 just wondering if there's going to be progress with this any time soon? I'm actively looking to replace fast_jsonapi with this now (trying to upgrade to ruby 3 and fast_jsonapi doesn't support that of course) and I still need this functionality for my app. Let me know if there's something I can do to help.

@stas
Copy link
Collaborator

stas commented May 13, 2021

Sorry @dvandersluis the #141 is usable but unstable at the moment. I'm doing my best trying to find time for it! Sorry 🙈

@dvandersluis
Copy link
Author

@stat That's ok I got my branch working with the current stable so it's "good enough" for me for now. Thanks for taking the reins on this gem and getting it working on 3.0!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Serialize collection of polymorphic objects

2 participants