Stopping a string being encoded within a hash Ruby on Rails

Viewed 245

I am trying to not jsonify a string within a hash. It already has been escaped.

Reading around the way of handling this for PORO is to overwrite as_json. So I wrapped the string in another object. But as I'm dealing with just a string that leads to a stack level too deep when I return the object. When I return the already encoded string, it obviously tries to escape it. activesupport-6.0.3.2/lib/active_support/json/encoding.rb

      def jsonify(value)
        case value
        when String
          EscapedString.new(value)
        when Numeric, NilClass, TrueClass, FalseClass
          value.as_json
        when Hash
          Hash[value.map { |k, v| [jsonify(k), jsonify(v)] }]
        when Array
          value.map { |v| jsonify(v) }
        else
          jsonify value.as_json
        end
      end

What I could do is monkey patch the above method to accept the class I have wrapped the string in and do nothing.

I guess the other option is to parse the JSON string back into a hash and let it follow the normal procedure. That would impact our performance too much though.

So my question is, Is there are more elegant way of telling Rails to do nothing when encountering a specified object when it tries to jsonify it.?

Edit:

Im using this already jsonified string like:

{foo: MyStringJsonWrapper.new('{"bar":"foobar"}')}.to_json
1 Answers

I was looking around in the file activesupport-6.0.3.2/lib/active_support/json/encoding.rb and noticed you can configure which class is used to perform the json encoding. It is currently JSONGemEncoder which is contained within that class.

So I made my own class:

class MyJSONEncoder < ActiveSupport::JSON::Encoding::JSONGemEncoder
  class AlreadyEscapedString < String
    def to_json(*)
      self
    end
  end

  private

  def jsonify(value)
    if value.is_a?(AlreadyEncodedStringWrapper)
      AlreadyEscapedString.new(value.already_jsonified)
    else
      super
    end
  end
end

and

class AlreadyEncodedStringWrapper
  attr_accessor :already_jsonified

  def initialize already_jsonified
    @already_jsonified = already_jsonified
  end

  def as_json(options = {})
    self
  end
end

The extra class of AlreadyEscapedString exists to overwrite the behaviour of EscapedString(found in the same encoding class) but do nothing.

Then just set:

ActiveSupport.json_encoder = MyJSONEncoder

Example:

jsonified_hash = {foobar: "barbaz"}.to_json

already_encoded_string = AlreadyEncodedStringWrapper.new(jsonified_hash)

{foo: already_encoded_string}.to_json
=> "{\"foo\": {\"foobar\": \"barbaz\"}}"
Related