Skip to content

Dropping MultiJson in 1.0.3 broke Time serialization for apps using ActiveSupport #403

Description

@Morxander

Description

After upgrading from grape-entity 1.0.1 to 1.0.3, all Time fields in API responses changed format for apps using ActiveSupport:

  • Before (1.0.1): "2026-04-16T10:55:10.597Z" (ISO 8601)
  • After (1.0.3): "2026-04-16 10:55:10 UTC"

This is a silent breaking change for API consumers expecting ISO 8601 timestamps.

Root cause

PR #385 replaced MultiJson.dump with JSON.dump in Entity#to_json: https://github.com/ruby-grape/grape-entity/pull/385/changes#diff-dede7a9b2a44333c10da3b7b006d011d8fddfc23ebaa64b415b5686c7798d296R561

MultiJson.dump internally calls Hash#to_json, which ActiveSupport intercepts to run as_json on all values — converting Time to ISO 8601. JSON.dump bypasses ActiveSupport entirely and serializes Time via its C extension using Time#to_s.

Reproduction

require 'json'
require 'active_support'
require 'active_support/core_ext/object/json'
require 'active_support/core_ext/time'
require 'grape_entity'

class TestEntity < Grape::Entity
  expose :published_at
end

t = Time.utc(2026, 4, 16, 10, 55, 10, 597000)
obj = OpenStruct.new(published_at: t)

TestEntity.represent(obj).to_json
# grape-entity 1.0.1  => {"published_at":"2026-04-16T10:55:10.597Z"}  ✅
# grape-entity 1.0.3 => {"published_at":"2026-04-16 10:55:10 UTC"}   ❌

Environment

  • Ruby 4.0.0
  • Rails 8.0.5
  • grape-entity 1.0.3

Activity

  1. dblock commented on Apr 16, 2026

    @dblock
    Member

    @Morxander want to PR reverting that change?

  2. stevenou commented on Apr 16, 2026

    @stevenou

    related error:

    1. grape-entity 1.0.1 did require 'multi_json' then used MultiJson.dump in to_json
    2. grape-entity 1.0.3 dropped the require 'multi_json' but still checks defined?(::MultiJson) in json.rb
    3. When json.rb loads, MultiJson isn’t loaded yet → falls back to Json = ::JSON
    4. Later, Grape framework loads MultiJson, but it’s too late
    5. JSON.dump doesn’t handle SimpleDelegator-wrapped hashes (the OutputBuilder) properly — it calls to_s instead of recognizing it as a Hash
  3. numbata commented on Apr 17, 2026

    @numbata
    Collaborator

    @dblock omw with reverting.

  4. added 2 commits that reference this issue on Apr 17, 2026
    374a021
    36cc468
  5. numbata commented on Apr 17, 2026

    @numbata
    Collaborator

    Thank you for the detailed report and reproduction, @Morxander. And @stevenou — good catch on the defined?(::MultiJson) load-order race, that was an additional blind spot.

    Here's the rollout plan:

    1. Patch release — Revert dropping MultiJson to fix Time serialization regression #405 reverts Drop multijson dependency #385 and restoresMultiJson.dump, bringing serialization back to the 1.0.1 behavior. This will ship as 1.0.4 so affected apps can pin or upgrade immediately.
    2. Minor release — Fix Time serialization regression from dropping MultiJson #404 drops MultiJson properly by using Hash#to_json instead of JSON.dump, which goes through ActiveSupport's as_json chain. This will land in a future minor version.

    Both PRs include regression tests for Time serialization in flat and nested exposures to prevent this going forward.

  6. self-assigned this
    on Apr 17, 2026
  7. numbata commented on Apr 17, 2026

    @numbata
    Collaborator

    Reverted in 1.0.4, restoring the previous Time serialization behavior.
    Closing this - proper removal of MultiJson is tracked in #404.

  8. added a commit that references this issue on Apr 17, 2026
    9cf5d0b
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions