Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

This project is archived and is in readonly mode.

ActiveSupport::JSON.encode fails for Struct types

#6077

I am using DataMapper's repository.adapter.select to retrieve results of an arbitrary query. The results are returned as an array of structs, each of which looks like the following:

#<struct matches_id=52276, begin_date=Sun, 28 Nov 2010 20:30:00 -0500, windows_time_zone="E. South America Standard Time", team1_id=1430, team2_id=373, team1="Avaí Futebol Clube", team2="Santos Futebol Clube", status="N", match_day_name="Rodada 37", season_name="Brasileirão Série A", round_name="Brasileirão Série A", competition_name="Brasileirão Série A", group_name="", use_gsm="Y", team1_score=0, team2_score=0>

Calling #to_json on this struct throws the following exception:

NoMethodError: undefined method `encode_json' for Sun, 28 Nov 2010 20:30:00 -0500:DateTime
    from /Users/mark/.rvm/gems/ruby-1.9.2-head/gems/activesupport-3.0.3/lib/active_support/json/encoding.rb:214:in `block in encode_json'
    from /Users/mark/.rvm/gems/ruby-1.9.2-head/gems/activesupport-3.0.3/lib/active_support/json/encoding.rb:214:in `map'
    from /Users/mark/.rvm/gems/ruby-1.9.2-head/gems/activesupport-3.0.3/lib/active_support/json/encoding.rb:214:in `encode_json'
    from /Users/mark/.rvm/gems/ruby-1.9.2-head/gems/activesupport-3.0.3/lib/active_support/json/encoding.rb:47:in `block in encode'
    from /Users/mark/.rvm/gems/ruby-1.9.2-head/gems/activesupport-3.0.3/lib/active_support/json/encoding.rb:77:in `check_for_circular_references'
    from /Users/mark/.rvm/gems/ruby-1.9.2-head/gems/activesupport-3.0.3/lib/active_support/json/encoding.rb:45:in `encode'
    from /Users/mark/.rvm/gems/ruby-1.9.2-head/gems/activesupport-3.0.3/lib/active_support/json/encoding.rb:30:in `encode'
    from /Users/mark/.rvm/gems/ruby-1.9.2-head/gems/activesupport-3.0.3/lib/active_support/core_ext/object/to_json.rb:20:in `to_json'
    from (irb):8
    from /Users/mark/.rvm/gems/ruby-1.9.2-head/gems/railties-3.0.3/lib/rails/commands/console.rb:44:in `start'
    from /Users/mark/.rvm/gems/ruby-1.9.2-head/gems/railties-3.0.3/lib/rails/commands/console.rb:8:in `start'
    from /Users/mark/.rvm/gems/ruby-1.9.2-head/gems/railties-3.0.3/lib/rails/commands.rb:23:in `<top (required)>'
    from script/rails:6:in `require'
    from script/rails:6:in `<main>'

The exception does not seem to be related to the DateTime type, since I can convert this struct to a hash and that will serialize to JSON without error:

Hash[row.members.zip(row.values)] => 

{:matches_id=>52276, :begin_date=>Sun, 28 Nov 2010 20:30:00 -0500, :windows_time_zone=>"E. South America Standard Time", :team1_id=>1430, :team2_id=>373, :team1=>"Avaí Futebol Clube", :team2=>"Santos Futebol Clube", :status=>"N", :match_day_name=>"Rodada 37", :season_name=>"Brasileirão Série A", :round_name=>"Brasileirão Série A", :competition_name=>"Brasileirão Série A", :group_name=>"", :use_gsm=>"Y", :team1_score=>0, :team2_score=>0}

which produces correct JSON on #to_json:

=> "{\"matches_id\":52276,\"begin_date\":\"2010-11-28T20:30:00-05:00\",\"windows_time_zone\":\"E. South America Standard Time\",\"team1_id\":1430,\"team2_id\":373,\"team1\":\"Ava\\u00ed Futebol Clube\",\"team2\":\"Santos Futebol Clube\",\"status\":\"N\",\"match_day_name\":\"Rodada 37\",\"season_name\":\"Brasileir\\u00e3o S\\u00e9rie A\",\"round_name\":\"Brasileir\\u00e3o S\\u00e9rie A\",\"competition_name\":\"Brasileir\\u00e3o S\\u00e9rie A\",\"group_name\":\"\",\"use_gsm\":\"Y\",\"team1_score\":0,\"team2_score\":0}"

Struct support should be added OR perhaps a pointer to some documentation on how I can expect AS::JSON in my own project to work with Struct. I think it's ok to do a local hack like the one I did temporarily, but I'd rather see this handled in Rails, or at the very least in some project specific override.

Thanks,
Mark

Reported by Mark A. Richman · November 28th, 2010 @ 05:46 AM

State: committed
Milestone: 3.x
Assigned to: Santiago Pastorino Santiago Pastorino
Importance: Low

Activity

  1. iain
    iain
    • Tag changed from json to_json encode struct to as_json, encode_json, json, to_json

    +1

    It's not really a problem with Structs, but with classes included in native structures.

    A test case:

    class Sub
      def as_json(*)
        { :foo => "bar" }
      end
    end
    
    class Base
      def as_json(*)
        [ Sub.new ]
      end
    end
    
    puts Base.new.to_json
    

    Resulting in the error reported:

    [GEM_HOME]/activesupport-3.0.3/lib/active_support/json/encoding.rb:214:in `encode_json': undefined method `encode_json' for #<Sub:0x102a02a78> (NoMethodError)
      from [GEM_HOME]/activesupport-3.0.3/lib/active_support/json/encoding.rb:214:in `map'
      from [GEM_HOME]/activesupport-3.0.3/lib/active_support/json/encoding.rb:214:in `encode_json'
      from [GEM_HOME]/activesupport-3.0.3/lib/active_support/json/encoding.rb:47:in `encode'
      from [GEM_HOME]/activesupport-3.0.3/lib/active_support/json/encoding.rb:77:in `check_for_circular_references'
      from [GEM_HOME]/activesupport-3.0.3/lib/active_support/json/encoding.rb:45:in `encode'
      from [GEM_HOME]/activesupport-3.0.3/lib/active_support/json/encoding.rb:30:in `encode'
      from [GEM_HOME]/activesupport-3.0.3/lib/active_support/core_ext/object/to_json.rb:15:in `to_json'
      from json_test.rb:15
    

    December 8th, 2010 @ 08:37 PM

  2. iain
    iain

    BTW: this issue was introduced in Rails 3.0.2, the above code works fine in Rails 3.0.1

    December 8th, 2010 @ 08:42 PM

  3. Mark A. Richman
    Mark A. Richman

    Thanks Iain! How do we get this fixed in 3.0.4?

    December 8th, 2010 @ 09:00 PM

  4. Mark A. Richman
    Mark A. Richman

    Would you mind pasting your json_test.rb so I can see what you did?

    December 8th, 2010 @ 09:02 PM

  5. iain
    iain

    json_test.rb is the code I showed (minus a "require 'config/environment'" line to load my Rails app).
    To fix it, we need to dig in activesupport/lib/active_support/json/encoding.rb and see what has been changed.

    December 8th, 2010 @ 09:33 PM

  6. John Firebaugh
    John Firebaugh

    A custom #as_json definition should usually call Array#as_json or Hash#as_json itself. For example, the correct definition in the example above is:

    class Base
      def as_json(*args)
        [ Sub.new ].as_json(*args)
      end
    end
    

    Object#as_json has a similar bug, which is causing Mark's exception. It should call #as_json on the result of #to_hash or #instance_values:

    class Object
      def as_json(options = nil) #:nodoc:
        if respond_to?(:to_hash)
          to_hash
        else
          instance_values
        end.as_json(options)
      end
    end
    

    December 9th, 2010 @ 12:58 AM

  7. bgallet
    bgallet

    I cannot reproduce this, however, it is not behaving as expected.

    Struct.new("StructTest", :name, :address)
    puts Struct::StructTest.new("Dave", "value").to_json # => ["Dave","121 Main st."]
    

    I would have expected: { "name": "Dave", "address": "121 Main st." }

    December 11th, 2010 @ 09:54 PM

  8. jasereja
    jasereja

    I have same problem.
    Use mongoid and try respond_to :json

    In rails 3.0.1 it work nice. higher versions broken.

    January 11th, 2011 @ 01:49 PM

  9. Alexey Nayden
  10. Alexey Nayden
    Alexey Nayden

    A patch to fix complex structures #as_json and #to_json

    January 13th, 2011 @ 12:22 AM

  11. Alexey Nayden
    Alexey Nayden
    • Assigned user set to Jeremy Kemper
    • Tag changed from as_json, encode_json, json, to_json to as_json, encode_json, json, patch, to_json

    January 13th, 2011 @ 12:35 AM

  12. Neeraj Singh
    Neeraj Singh
    • Milestone set to 3.x
    • State changed from new to open
    • Importance changed from to Low

    January 13th, 2011 @ 02:46 AM

  13. Neeraj Singh
    Neeraj Singh

    sorry I did not see there was already a patch for it. Ignore me.

    January 13th, 2011 @ 02:01 PM

  14. Alexey Nayden
    Alexey Nayden

    @Neeraj no problem. So are you going to apply my patch? Or maybe I shall reassign the ticket to someone else?

    January 13th, 2011 @ 08:47 PM

  15. Neeraj Singh
    Neeraj Singh

    I can't apply your patch. Only core team can do that. I can +1 this ticket.

    +1

    January 13th, 2011 @ 10:57 PM

  16. Alexey Nayden
    Alexey Nayden
    • Assigned user changed from Jeremy Kemper to Santiago Pastorino

    January 13th, 2011 @ 11:21 PM

  17. Alexey Nayden
    Alexey Nayden

    Santiago, would you please apply my patch? It seems to be useful bugfix.

    January 13th, 2011 @ 11:23 PM

  18. Repository
  19. Bryce Kerley
    Bryce Kerley

    I'm getting a test failure on this in 1.9.2-p136, due to hash ordering:

    test_struct_encoding(TestJSONEncoding) [test/json/encoding_test.rb:239]:
    <"{\"sub\":{\"name\":\"David\",\"date\":\"2010/01/01\"},\"name\":\"David\"}"> expected but was
    <"{\"name\":\"David\",\"sub\":{\"name\":\"David\",\"date\":\"2010/01/01\"}}">.
    

    Should the expected struct ordering be canonical? If not, I've patched the expected ordering in the test at https://github.com/bkerley/rails/commit/b9d05c3fff974e59b4e06da68d9... .

    January 17th, 2011 @ 04:24 AM

  20. Mike (at coverallcrew)
    Mike (at coverallcrew)

    This is still failing for me with Rails 3.0.4

    To replicate, try using Mongoid or MongoMapper with anything higher than Rails 3.0.1

    February 11th, 2011 @ 08:28 PM

  21. Santiago Pastorino
    Santiago Pastorino

    Have you tried this against master?
    I guess we haven't backported to 3-0-stable?

    February 11th, 2011 @ 10:24 PM