Skip to content

Accessing a nested structure results in a NoMethodError #440

@Yang-33

Description

@Yang-33

example: #439

--

The body of an HTTP response is often JSON. Currently, after slightly modifying the JSON keys, we use class.new(**json) (which calls class#initialize) to assign the values to the class attributes.

This approach works only when there are no nested keys. For example, the following JSON structure is acceptable:

{
  "phase": "waiting"
}

We can access the value using response.phase.

However, when there are nested keys, we cannot access those keys by directly traversing the attributes.

{
    "audienceGroup": {
        "audienceGroupId": 12345
    }
}

response.audienceGroup exists, but response.audienceGroup.audienceGroupId results in a NoMethodError.(the test failed due to this reason)

This occurs because the initialize method of the class handling the response does not instantiate another class for the values of JSON objects. Instead, it directly assigns them to the attributes of its own class. Consequently, response.audienceGroup remains a Hash or JSON object.

The current class implementation is the cause of this issue:

class ClassForAudienceGroupResponse
  attr_accessor :audience_group
  def initialize(
    audience_group # This is a Hash/JSON containing { "audienceGroupId": 12345 }
  )
    @audience_group = audience_group # Simply assign it
  end
end

(actual code:

class GetAudienceGroupsResponse
attr_accessor :audience_groups # An array of audience data. If there are no audiences that match the specified filter, an empty array will be returned.
attr_accessor :has_next_page # true when this is not the last page.
attr_accessor :total_count # The total number of audiences that can be returned with the specified filter.
attr_accessor :read_write_audience_group_total_count # Of the audiences you can get with the specified filter, the number of audiences with the update permission set to READ_WRITE.
attr_accessor :page # The current page number.
attr_accessor :size # The maximum number of audiences on the current page.
def initialize(
audience_groups: nil,
has_next_page: nil,
total_count: nil,
read_write_audience_group_total_count: nil,
page: nil,
size: nil
)
@audience_groups = audience_groups
@has_next_page = has_next_page
@total_count = total_count
@read_write_audience_group_total_count = read_write_audience_group_total_count
@page = page
@size = size

)

Therefore, i think it would be better to implement the following change as of now:

class ClassForAudienceGroupResponse
  attr_accessor :audience_group
  def initialize(
    audience_group # This is a Hash/JSON containing { "audienceGroupId": 12345 }
  )
    @audience_group = AudienceGroup.new(**audience_group) # Instantiate AudienceGroup with the provided data
  end
end

Advantages and Disadvantages:

Advantages:

  • We can access nested values by traversing attributes.
  • Handling response models and webhook models becomes consistent, eliminating the need for manual webhook parsing.
  • We can remove the dependency on OpenStruct.

Disadvantages:

  • When dealing with polymorphism (e.g., type: text, type: image), we will need to implement logic to determine the appropriate class for deserialization (this is already implemented in Python and PHP, so it should be achievable).
  • When dealing with polymorphism, we will need to implement a fallback mechanism to an UnknownHogeClass if an older SDK receives a new type (e.g., type: newType) that is released in Messaging API newly. This is already implemented in Python, PHP, and Java.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions