Skip to content

Fix Time serialization regression from dropping MultiJson#404

Open
numbata wants to merge 3 commits intomasterfrom
fix/json-dump-time-serialization
Open

Fix Time serialization regression from dropping MultiJson#404
numbata wants to merge 3 commits intomasterfrom
fix/json-dump-time-serialization

Conversation

@numbata
Copy link
Copy Markdown
Collaborator

@numbata numbata commented Apr 17, 2026

Summary

Version 1.0.3 introduced a silent breaking change for all API consumers: Time fields switched from ISO 8601 format ("2026-04-16T10:55:10.597Z") to Ruby's default Time#to_s format ("2026-04-16 10:55:10 UTC"). This breaks any client parsing timestamps from the API.

What happened

PR #385 replaced MultiJson.dump with JSON.dump in Entity#to_json to drop the multi_json dependency. While the intent was correct, JSON.dump and MultiJson.dump behave differently when ActiveSupport is present:

  • MultiJson.dump(hash) internally calls Hash#to_json, which ActiveSupport overrides to run as_json recursively on all values — giving Time the ISO 8601 representation.
  • JSON.dump(hash) goes directly to Ruby's C extension JSON generator, bypassing ActiveSupport's Hash#to_json override entirely — so Time falls back to Time#to_s.

Since ActiveSupport is a runtime dependency of grape-entity, every user is affected.

An additional problem noted by @stevenou: the intermediate Grape::Entity::Json constant used defined?(::MultiJson) at require time to decide which backend to use. For apps with gem "multi_json", require: false, MultiJson wasn't loaded yet at that point, so it always fell back to ::JSON regardless — making the MultiJson detection dead code in practice.

How this fix works

Replaces Grape::Entity::Json.dump(serializable_hash(options)) with serializable_hash(options).to_json.

Hash#to_json is the method ActiveSupport overrides. This single change resolves all reported problems:

  1. Time serializationHash#to_json goes through ActiveSupport's as_json chain, restoring ISO 8601 output for Time and any other type with an as_json override.
  2. Nested exposuresOutputBuilder (a SimpleDelegator wrapping a Hash) properly delegates .to_json to the underlying Hash.
  3. MultiJson load-order race — eliminated entirely. We no longer check for or use MultiJson at all, so require: false scenarios are irrelevant.

The now-unused Grape::Entity::Json constant and grape_entity/json.rb are removed.

Fixes #403

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 17, 2026

Danger Report

No issues found.

View run

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes a regression introduced when switching from MultiJson.dump to JSON.dump by restoring ActiveSupport-aware JSON serialization for Time values in Grape::Entity#to_json.

Changes:

  • Switch Entity#to_json to serialize via serializable_hash(...).to_json to re-enable ActiveSupport’s as_json chain (restoring ISO 8601 Time output).
  • Remove the now-unused Grape::Entity::Json shim and its spec.
  • Add specs asserting Time serialization uses as_json, including for nested exposures.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
spec/grape_entity/json_spec.rb Removes spec for the deleted Grape::Entity::Json constant.
spec/grape_entity/entity_spec.rb Adds regression coverage for Time serialization (top-level + nested exposures).
lib/grape_entity/json.rb Removes the Grape::Entity::Json backend-selection shim.
lib/grape_entity/entity.rb Updates #to_json implementation to call to_json on the serializable output (ActiveSupport-aware).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/grape_entity/entity.rb
numbata added 2 commits April 17, 2026 06:38
JSON.dump bypasses ActiveSupport's Hash#to_json override, causing
Time values to serialize via Time#to_s ("2026-04-16 10:55:10 UTC")
instead of ActiveSupport's as_json ("2026-04-16T10:55:10.597Z").

Replace Json.dump with Hash#to_json which properly delegates through
ActiveSupport and remove the now-unused Grape::Entity::Json constant.

Fixes #403
@numbata numbata force-pushed the fix/json-dump-time-serialization branch from 374a021 to 09ed8be Compare April 17, 2026 04:39
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a serialization regression introduced when switching JSON backends by ensuring Time (and other ActiveSupport-enhanced types) are serialized through ActiveSupport’s as_json chain again, restoring ISO 8601 timestamp output for API consumers.

Changes:

  • Switch Grape::Entity#to_json to serialize via serializable_hash(...).to_json (ActiveSupport-aware) instead of MultiJson.dump.
  • Remove the multi_json runtime dependency from the gemspec.
  • Document the fix and dependency removal in the changelog.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
lib/grape_entity/entity.rb Restores ActiveSupport-aware JSON serialization by using Hash#to_json on the computed serializable output.
grape-entity.gemspec Drops the multi_json dependency now that it’s no longer used.
CHANGELOG.md Notes the regression fix and dependency removal in the Unreleased fixes section.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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.

Dropping MultiJson in 1.0.3 broke Time serialization for apps using ActiveSupport

2 participants