From ab01f9f3da837395550c0b20d3349aad9d314e38 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 29 Jun 2023 14:12:00 -0700 Subject: [PATCH] Consider Symbol "JSON-ready", improve jsonify Previously jsonify would call `.as_json` for Integer, nil, true, and false, even though those types are considered "JSON-ready". Technically a user could have overridden `.as_json` for these types but I can't imagine a use case and I don't think we should support that. I left the same behaviour of calling `.as_json` for generic "Numeric" as that can have user subclasses where one may have implemented as_json. This behaviour is also used for Float (which coerces NaN/Infinity/-Infinity into nil). This also adds Symbol to the list of "JSON-ready" types, to avoid unnecessarily casting them to strings (possible as we no longer perform escaping on input). The output of jsonify should never be user visible before it is passed through JSON.generate, so I don't think this should be a user facing change. This also corrects our handling of Hash to call to_s on all keys, matching the behaviour of `.as_json` and JSON's requirement that keys are Strings (Symbols are also permitted as JSON knows to convert them to a String). --- .../lib/active_support/json/encoding.rb | 9 ++++--- .../test/json/encoding_test_cases.rb | 25 ++++++++++++++++++- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index e9e54213101..5b4f8e873f4 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -67,7 +67,7 @@ module ActiveSupport :ESCAPE_REGEX_WITHOUT_HTML_ENTITIES # Convert an object into a "JSON-ready" representation composed of - # primitives like Hash, Array, String, Numeric, + # primitives like Hash, Array, String, Symbol, Numeric, # and +true+/+false+/+nil+. # Recursively calls #as_json to the object to recursively build a # fully JSON-ready object. @@ -81,14 +81,15 @@ module ActiveSupport # calls. def jsonify(value) case value - when String + when String, Integer, Symbol, nil, true, false value - when Numeric, NilClass, TrueClass, FalseClass + when Numeric value.as_json when Hash result = {} value.each do |k, v| - result[jsonify(k)] = jsonify(v) + k = k.to_s unless Symbol === k || String === k + result[k] = jsonify(v) end result when Array diff --git a/activesupport/test/json/encoding_test_cases.rb b/activesupport/test/json/encoding_test_cases.rb index 662e30c68c0..471ffd327be 100644 --- a/activesupport/test/json/encoding_test_cases.rb +++ b/activesupport/test/json/encoding_test_cases.rb @@ -36,6 +36,26 @@ module JSONTest end end + class RomanNumeral < Numeric + def initialize(str) + @str = str + end + + def as_json(options = nil) + @str + end + end + + class CustomNumeric < Numeric + def initialize(str) + @str = str + end + + def to_json(options = nil) + @str + end + end + module EncodingTestCases TrueTests = [[ true, %(true) ]] FalseTests = [[ false, %(false) ]] @@ -46,7 +66,10 @@ module JSONTest [ 1.0 / 0.0, %(null) ], [ -1.0 / 0.0, %(null) ], [ BigDecimal("0.0") / BigDecimal("0.0"), %(null) ], - [ BigDecimal("2.5"), %("#{BigDecimal('2.5')}") ]] + [ BigDecimal("2.5"), %("#{BigDecimal('2.5')}") ], + [ RomanNumeral.new("MCCCXXXVII"), %("MCCCXXXVII") ], + [ [CustomNumeric.new("123")], %([123]) ] + ] StringTests = [[ "this is the ", %("this is the \\u003cstring\\u003e")], [ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ],