简体   繁体   中英

how do i check that all attributes/values in a class or json object are nil

I have a method that retrieves an elasticsearch query from an API and converts it to an array of JSON objects.

In some cases, the last JSON object is not a nil object, but all the attributes have nil values.

I'd like to avoid mapping over every object, but I need to filter out those JSON objects that have all nil values.

for example:

=> [#<Api::User:0x006546546
      @user_id=1,
      @height=70,
      @age=25>,
    #<Api::User:0x006546542
      @user_id=nil
      @height=nil
      @age=nil>]

I want to remove the all-nil object from the array either while it is in JSON format or after it is converted to an array of Api::User objects.

Is a map the only way to check all values of all objects or is there a less resource-intensive method?

I'd lean heavy on methods that Enumerable and Object give you. From Enumerable we can use select to choose only those elements of the array that meet some condition. That condition is that at least one of the instance variables is not nil , and for that we can use any? .

From Object we will use instance_variables to get a collection of instance variables, and we will access the value of those instance variables with instance_variable_get .

It would look something like this:

filtered_array = array.select do |object| 
  object.instance_variables.any? { |var| object.instance_variable_get(var) }
end

Of course any? is efficient in that it will stop iteration and return true on the very first "truthy" variable. As suggested by SteveTurczyn if you only need to check the last value, it is even quicker.

filtered_array = array[0..-2] if array[-1].instance_variables.none? { |var| object.instance_variable_get(var) }

Someone may come up with a better way, but it seems you don't need to check all values of all objects, as the first value you check that's non-nil means that object is good. I'd do something that checks each value up to the point that a non-nil value is found. It would also help efficiency if you can order tested fields with most likely to have a value first (eg user_id )

array.select do |object|
  %i(user_id height age).find do |field_name| 
    object.send(field_name)
  end
end

If it's only the last object that needs to be tested, then even easier...

array.pop if %i(user_id height age).find {|field_name| array.last.send(field_name) }

I'm sure there is a cleaner way, but just in case you need to map each object:

def are_attributes_not_nil?(my_object)
  my_object.instance_variables.map{ |attr| my_object.send(attr.to_s[1..-1]).nil? }.uniq.include? false
end

returns false if all attributes are nil

My solution involves first implementing a way to convert the objects into hashes before turning them into the JSON itself.

api_user_array #=> [#<Api::User:0x006546546 ... >, #<Api::User:0x006546542 ... >]
api_user_array = api_user_array.map(&:to_h).reject { |hash| hash.values.all?(&:nil?) }
# should result in an array of hashes with at least one value not nil

This assumes that you implement the Api::User#to_h method. This could be something like this:

def attribute_names
  %w[user_id height age]
end

def to_h
  attribute_names.each_with_object({}) { |attr, hash| hash[attr] = send(attr) }
  # assuming all attributes have a getter, otherwise
  attribute_names.each_with_object({}) { |attr, hash| hash[attr] = instance_variable_get("@#{attr}") }
end

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM