如何在Jbuilder中注入缓存的JSON文本
Jbuilder提供了方便、强大的DSL来生成JSON。在最新版本的Jbuilder(v2.x)中也提供了cache的API来提高性能。
在我的一个项目中,要求非常高的JSON生成效率。于是我尝试使用Jbuilder的cache API来实现。但是结果是性能反而下降了。仔细研究后,才发现Jbuilder的原理是各个节点要么是Array,要么是Hash,而它的cache也只是简单的缓存生成好的Array或者Hash,然后在merge到当前的JSON数据中。在我的项目中,需要序列化成JSON的数据都已经在内存里面了,所以缓存Array或者Hash,反而是增加了损耗。我需要找到的是直接在Jbuilder的DSL中注入JSON文本的功能。
我找到的方法原理是通过创建一个CompiledJson的Class,覆盖to_json的方法来直接返回JSON文本。
require 'oj'
class CompiledJson
def initialize(s); @s = s; end
def to_json(*args); @s; end
def to_s; @s; end
undef_method :as_json
end
Oj.default_options = {:mode => :compat, use_to_json: true}
result = Jbuilder.new do |json|
json.a CompiledJson.new('{x: "y"}')
end
puts result.target!
# => {"a": {x: "y"}}
Jbuilder的主要贡献者@rwz给出了更贴切的方法:
# config/initializers/lol_json.rb
module ActiveSupport
module JSON
module Encoding
class JSONGemEncoder
BYPASS_JSONIFY = Set.new
alias_method :original_jsonify, :jsonify
def jsonify(value)
BYPASS_JSONIFY.include?(value.class) ? value : original_jsonify(value)
end
end
end
end
end
# lib/compiled_json.rb
class CompiledJson
def initialize(s); @s = s; end
def to_json(*); @s; end
def as_json(*); self; end
end
ActiveSupport::JSON::Encoding::JSONGemEncoder::BYPASS_JSONIFY << CompiledJson
# seems to be working
bar = CompiledJson.new("bar")
MultiJson.dump(foo: bar) # => { "foo": bar }
{ foo: bar }.to_json # => { "foo": bar }
Jbuilder.encode{ |json| json.foo bar } # => { "foo": bar }
需要注意的是因为在Rails4.1之后,ActiveSupport改变了JSON的生成机制,所以两个方法都涉及到覆盖其默认的方式。
https://gist.github.com/rwz/14c0d8a5187b69922bb4 https://github.com/rails/jbuilder/issues/204#issuecomment-54004919 https://github.com/ohler55/oj/issues/140