From 4d4b5d16b7b17d72abf7aac40214617cd2bd2a53 Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Sun, 21 Jun 2026 08:03:20 +0200 Subject: [PATCH] Always format JSON via Grape::Json.dump Grape::Formatter::Json and Grape::Formatter::SerializableHash short-circuited to object.to_json whenever the object responded to it. Since nearly every object responds to to_json, Grape::Json.dump was effectively never reached and a configured multi_json back-end (e.g. oj) was bypassed during formatting. Remove the to_json short-circuit so both formatters always serialize through Grape::Json.dump, honoring the JSON back-end consistently with Grape::ErrorFormatter::Json and Grape::Parser::Json. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 1 + README.md | 2 +- UPGRADING.md | 8 ++++++++ lib/grape/formatter/json.rb | 2 -- lib/grape/formatter/serializable_hash.rb | 1 - 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 339d3a518..68e3c47b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ #### Fixes +* [#2769](https://github.com/ruby-grape/grape/pull/2769): Always format JSON via `Grape::Json.dump` so a configured `multi_json` back-end is honored - [@ericproulx](https://github.com/ericproulx). * Your contribution here. ### 3.3.0 (2026-06-20) diff --git a/README.md b/README.md index 84a4fcac5..7af8856e9 100644 --- a/README.md +++ b/README.md @@ -3160,7 +3160,7 @@ end Built-in formatters are the following. -* `:json`: use object's `to_json` when available, otherwise call `MultiJson.dump` +* `:json`: serializes the object via Grape's JSON back-end (`JSON` by default, or `multi_json` when available) * `:xml`: use object's `to_xml` when available, usually via `MultiXml` * `:txt`: use object's `to_txt` when available, otherwise `to_s` * `:serializable_hash`: use object's `serializable_hash` when available, otherwise fallback to `:json` diff --git a/UPGRADING.md b/UPGRADING.md index 03235066e..932e4c389 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,6 +1,14 @@ Upgrading Grape =============== +### Upgrading to >= 4.0 + +#### The `:json` and `:serializable_hash` formatters no longer call `to_json` + +`Grape::Formatter::Json` and `Grape::Formatter::SerializableHash` previously short-circuited to `object.to_json` whenever the object responded to it. Since virtually every object responds to `to_json`, this meant `Grape::Json.dump` was effectively never reached and a configured `multi_json` back-end (e.g. `oj`) was bypassed during formatting. Both formatters now always serialize through `Grape::Json.dump`, so the JSON back-end is honored consistently (matching `Grape::ErrorFormatter::Json` and `Grape::Parser::Json`). + +If you relied on a custom `to_json` for response formatting, either register your own formatter or make `Oj.mimic_JSON` (or your serializer of choice) the active back-end. + ### Upgrading to >= 3.3 #### Minimum required Ruby is now 3.3 diff --git a/lib/grape/formatter/json.rb b/lib/grape/formatter/json.rb index bfdd2ca28..3b2fa4687 100644 --- a/lib/grape/formatter/json.rb +++ b/lib/grape/formatter/json.rb @@ -4,8 +4,6 @@ module Grape module Formatter class Json < Base def self.call(object, _env) - return object.to_json if object.respond_to?(:to_json) - ::Grape::Json.dump(object) end end diff --git a/lib/grape/formatter/serializable_hash.rb b/lib/grape/formatter/serializable_hash.rb index 5b2294668..e2e4555e7 100644 --- a/lib/grape/formatter/serializable_hash.rb +++ b/lib/grape/formatter/serializable_hash.rb @@ -7,7 +7,6 @@ class << self def call(object, _env) return object if object.is_a?(String) return ::Grape::Json.dump(serialize(object)) if serializable?(object) - return object.to_json if object.respond_to?(:to_json) ::Grape::Json.dump(object) end