oj
oj copied to clipboard
Oj.generate: don't call as_json on rails model
Hi,
Since I upgraded to Oj 3.11.4 (from 3.11.2) json serialization of rails models don't work as expected.
# frozen_string_literal: true
require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
# Activate the gem you are reporting the issue against.
gem "activerecord", "~> 6.1.0"
gem "sqlite3"
gem "oj", "3.11.4"
end
require "active_record"
require "minitest/autorun"
require "logger"
require "oj"
Oj.optimize_rails
# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Schema.define do
create_table :posts, force: true do |t|
t.string :title
t.string :body
end
end
class Post < ActiveRecord::Base
end
post = Post.create!(title: "Hello", body: "World")
puts Oj.generate({ post: post })
With Oj 3.11.2, the output is:
{"post":{"id":1,"title":"Hello","body":"World"}}
And with 3.11.4, the output is:
{"post":"#<Post:0x00007f808b8b30b8>"}
Am I missing configuration step ? or is it a bug?
Have you tried the most recent Oj? 3.11.4 is 5 months out of date.
@ohler55 same with 3.13.7
, I upgraded from 3.11.2
and did manual bisect to find the version who caused this bug.
I see, okay will try to identify what changed and if it is different than what active support does. Thanks for the very clear way to reproduce.
https://github.com/ohler55/oj/blob/develop/CHANGELOG.md#3114---2021-04-14
Fixed compatibility issue with Rails and the JSON gem that requires a special case when using JSON.generate versus call to_json on an object.
https://github.com/ohler55/oj/issues/651
Oj.generate now behaves the same as JSON.generate.
If you expect {"post":{"id":1,"title":"Hello","body":"World"}}
,
Oj.dump({ post: post })
or { post: post }.to_json
seems to good to use.
@kmasuda-aiming thanks! so in some places I will need to use Oj.dump
.
I found an issue with Oj.dump
, same reproduction script (I posted on issue body) with oj 3.13.8
and added those lines:
post_obj = { post: post }
puts Oj.generate(post_obj)
puts post_obj.to_json
puts Oj.dump(post_obj)
And Oj.dump
fails (Oj.generate
and .to_json
are working) -
Traceback (most recent call last):
1: from oj_bug.rb:42:in `<main>'
oj_bug.rb:42:in `dump': Too deeply nested. (NoMemoryError)
If I put mode: :rails
in Oj.dump
call - it works.
Any idea why?
Without knowing what the active record layout is I would guess there is an object that implements #as_json
by returning itself instead of a Hash or in some cases I have seen it actually calls generate in the #as_json
. In rails mode Oj takes some additional steps to catch that. The other modes don't bother as according to the docs #as_json
always returns a Hash. Oddly enough active support started the use of #as_json
and so far that is the only gem I have seen violate their own documentation.
I'm sorry I gave you the wrong information.
In the case of mode: :object
, it generates a string like the blew.
{"^o":"Post","new_record":false,"attributes":{"^o":"ActiveModel::AttributeSet","attributes":{"id":{"^o":"ActiveModel::Attribute::FromDatabase","name":"id","value_before_type_cast":1,"type":{"^o":"ActiveRecord::ConnectionAdapters::SQLite3Adapter::SQLite3Integer","precision":null,"scale":null,"limit":null,"range":{"^u":["Range",-9223372036854775808,9223372036854775808,true]}},"original_attribute":null,"value":1},"title":{"^o":"ActiveModel::Attribute::FromDatabase","name":"title","value_before_type_cast":"Hello","type":{"^o":"ActiveModel::Type::String","true":"t","false":"f","precision":null,"scale":null,"limit":null},"original_attribute":null,"value":"Hello"},"body":{"^o":"ActiveModel::Attribute::FromDatabase","name":"body","value_before_type_cast":"World","type":{"^o":"ActiveModel::Type::String","true":"t","false":"f","precision":null,"scale":null,"limit":null},"original_attribute":null,"value":"World"}}},"association_cache":{},"primary_key":"id","readonly":false,"previously_new_record":true,"destroyed":false
mode: :json
( same as mode: :compat
) or mode: :rails
generates {"post":{"id":1,"title":"Hello","body":"World"}}
.
Oj.dump(post_obj, mode: :rails)
to change the mode.
If you want to change the settings for the whole system, you can use Oj.default_options = { mode: :rails }
.
I think this issue can be closed. Any objections?