BigDecimal encoded as String in JSON

I experienced a major headache when upgrading to Rails 5 to continue encoding my BigDecimal values as decimals instead of Strings ( I ended up pulling in Oj gem and setting Oj.default_options = { bigdecimal_as_decimal: true } ).

I’m now trying to update to Rails 6, and have found that this once again has regressed due to Fix AM::Serializers::JSON#as_json method for timestamps by bogdan · Pull Request #31503 · rails/rails · GitHub. It is immensely frustrating to have to once again find a workaround for what should be a simple configuration parameter (encode_big_decimal_as_string, anyone??).

I have been through Do NOT return BigDecimal as string in JSON - breaks DynamoDB support · Issue #25017 · rails/rails · GitHub countless times, and I always found the tone so very hostile. Our organization understands the risks involved,if we could rewrite our API to use the default then we would do that in a heartbeat, but we have legacy APIs to maintain and can’t just change the type of attributes in our responses. It just feels like Rail’s stance on this is overly-opinionated and is preventing the programmer from making their own choices about how their app behaves.

1 Like

I’ve run into this a few times with new applications. It strings extra because I “tried to the the right thing”, but ended up having to bypass the default serializer. I declared decimals with the required precision and scale in the database and on the client side, but Rails ate my number.

From the reason given on the issue you link to:

in practice all clients would parse that into a float/double data type

In my case this was not true. The client library (iOS/Swift JSONDecoder) expects Decimal as a number and won’t accept a string.

Thank you for bringing this up. Does this gem work for you https://github.com/rails/activesupport-json_encoder?

Another solution I saw is to define a as_json in BigDecimal doesn’t that solution work?

Thank you for the response. We ended up overriding as_json in BigDecimal as you describe:

class BigDecimal
  def as_json
    to_d
  end
end

This seems to give us exactly what we need without dropping precision.

I think some of my frustration can be summarized in this issue https://github.com/rails/rails/issues/37604. Looking through the Rails changelog, I had no idea that all attributes would be forced back through as_json, since the referenced PR only mentions timestamps. Luckily we had regression tests that caught this, but it seems like there was not consideration for the broad impact that the change would have, it would have been great if this information could have been included somewhere in the changelog as suggested in the linked issue above.

Of course I understand that it is impossible to account for all changes and the impact they could potentially have, and I applaud the Rails team for doing what you guys do :hugs:.

This seems like a case where documentation in “the right place” might have helped. @PhilCoggins when you first tried to solve this issue, where was the first place you looked?

1 Like

It has been quite a while since I first encountered this, but I probably started on google and then moved to Issues · rails/rails · GitHub

It doesn’t work with rails 7,

By overriding as_json in BigDecimal, when we call render json: { success: true, message: message, data: data }, it still render decimal as string in json.

class BigDecimal
  def as_json(options = nil)
    to_f
  end
end

to_f will work in Rails 7, to_d doesn’t work!