Skip to content

Grape::Entity#as_json does not return a recursive set of hashes.  #351

Description

@synth

Given: A Grape::Entity that exposes another Grape::Entity
Expected: When calling .as_json (or serializable_hash) it should recursively convert all objects to hashes
Actual: Only the top level is converted, nested entities remain as: Grape::Entity::Exposure::NestingExposure::OutputBuilder

Discussed tangentially in: #39, #299, #313

The reason why perhaps this isn't more of an issue is that .to_json works correctly because that returns output from MultiJson:

def to_json(options = {})
options = options.to_h if options&.respond_to?(:to_h)
MultiJson.dump(serializable_hash(options))
end

Indeed, .as_json is just an alias for serializable_hash which is what isn't properly recursive.

alias as_json serializable_hash

This is easily reproducible:

class TestCompany
  attr_accessor :domain
end

class TestUser
  attr_accessor :name
  attr_accessor :company
end

class TestCompanyEntity < Grape::Entity
  expose :domain
end

class TestUserEntity < Grape::Entity
  expose :name
  expose :company, using: TestCompanyEntity
end

tc = TestCompany.new.tap{|tc| tc.domain = "https://example.com" }
tu = TestUser.new.tap{|tu| tu.name = "John"}
tu.company = tc

tue = TestUserEntity.new(tu)

[26] pry(main)> tue.serializable_hash
=> {:name=>"John", :company=>{:domain=>"https://example.com"}}
[27] pry(main)> tue.serializable_hash.deep_stringify_keys
=> {"name"=>"John", "company"=>{:domain=>"https://example.com"}} # notice :domain is still a symbol
[28] pry(main)> tue.serializable_hash[:company].class # that's because company isn't a hash despite kinda looking like one
=> Grape::Entity::Exposure::NestingExposure::OutputBuilder

A simple workaround is for developers is to send it through JSON dump/parse

JSON.parse(JSON.dump(tue.serializable_hash))

But this seems unnecessary work and would be nice if this gem implemented recursive output at a higher level than just .to_json which gives us a string not a hash.

PS. Thank you to everyone who has worked on this gem! ❤️

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions