2

My respect to community! I have deep nested hash and I want to transform all values of specific key provided. Something like deep transform values if key == :something Example of hash:

{:id=>"11ed35b8e53c442ea210c39d6f24bddf",
 :createdAt=>"2022-09-16T12:12:55.454Z",
 :updatedAt=>"2022-09-16T12:12:55.454Z",
 :status=>"ACTIVE",
 :description=>"test",
 :goals=>
  [{:Definitions=>[],
    :text=>"Follows Action Plan Appropriately, Worse or Warning Symptoms",
    :status=>"ACTIVE",
    :Definitions=>[{:text=>"Search for relics", :status=>"INACTIVE", :id=>"11ec1fc4bd36f876963867013cee2799"}],
    :id=>"11e818be2f0157329c76634ee23c1d8f"}],
 :healthConcernDefinitions=>[]}

I want to transform all values of all keys :status Result must be:

{:id=>"11ed35b8e53c442ea210c39d6f24bddf",
 :createdAt=>"2022-09-16T12:12:55.454Z",
 :updatedAt=>"2022-09-16T12:12:55.454Z",
 :status=>"TRANSFORMED",
 :description=>"test",
 :goals=>
  [{:Definitions=>[],
    :text=>"Follows Action Plan Appropriately, Worse or Warning Symptoms",
    :status=>"TRANSFORMED",
    :Definitions=>[{:text=>"Search for relics", :status=>"TRANSFORMED", :id=>"11ec1fc4bd36f876963867013cee2799"}],
    :id=>"11e818be2f0157329c76634ee23c1d8f"}],
 :healthConcernDefinitions=>[]}

I have a couple solutions First one:

hash_to_json = hash.to_json.gsub(/"ACTIVE"|"INACTIVE"/, '"TRANSFORMED"')
JSON.parse(hash_to_json)

Second one:

hash.deep_transform_values { |v| (v == 'ACTIVE' || v == 'INACTIVE') ? 'TRANSFORMED' : v }

Both solutions are working but I do not like to match values, I want to match only specific key and change it all over the hash, somting like:

hash.deep_transform_keys { |k, v| v = 'TRANSFORMED' if k == :status }

Thanks a lot!!!

mechnicov
  • 12,025
  • 4
  • 33
  • 56
Transfer
  • 41
  • 3

2 Answers2

2

Unfortunately Hash#deep_transform_values takes in block only values, not keys. And unfortunately Hash#deep_merge and Hash#deep_merge! are not so powerful

You can write your own method(s). It is just idea, you can improve it

class Hash
  def deep_replace(hash)
    deep_dup.deep_replace!(hash)
  end

  def deep_replace!(hash)
    each_key do |key|
      hash.each do |k, v|
        self[key] = v if key == k
      end

      _replace_object!(self[key], hash)
    end
  end

  private

  def _replace_object!(object, hash)
    case object
    when Hash
      object.deep_replace!(hash)
    when Array
      object.map! { |v| _replace_object!(v, hash) }
    else
      object
    end
  end
end
hash = {:id=>"11ed35b8e53c442ea210c39d6f24bddf",
 :createdAt=>"2022-09-16T12:12:55.454Z",
 :updatedAt=>"2022-09-16T12:12:55.454Z",
 :status=>"ACTIVE",
 :description=>"test",
 :goals=>
  [{:Definitions=>[{:text=>"Search for relics", :status=>"INACTIVE", :id=>"11ec1fc4bd36f876963867013cee2799"}],
    :text=>"Follows Action Plan Appropriately, Worse or Warning Symptoms",
    :status=>"ACTIVE",
    :id=>"11e818be2f0157329c76634ee23c1d8f"}],
  :healthConcernDefinitions=>[]}

And after that you can apply it

# Replace one key
hash.deep_replace(status: "TRANSFORMED")

# => {:id=>"11ed35b8e53c442ea210c39d6f24bddf",
# :createdAt=>"2022-09-16T12:12:55.454Z",
# :updatedAt=>"2022-09-16T12:12:55.454Z",
# :status=>"TRANSFORMED",
# :description=>"test",
# :goals=>
#  [{:Definitions=>[{:text=>"Search for relics", :status=>"TRANSFORMED", :id=>"11ec1fc4bd36f876963867013cee2799"}],
#    :text=>"Follows Action Plan Appropriately, Worse or Warning Symptoms",
#    :status=>"TRANSFORMED",
#    :id=>"11e818be2f0157329c76634ee23c1d8f"}],
#  :healthConcernDefinitions=>[]}
# Replace few keys
hash.deep_replace(status: "TRANSFORMED", txid: "555", id: "99999999999999999999999999999999")

# => {:id=>"99999999999999999999999999999999",
# :createdAt=>"2022-09-16T12:12:55.454Z",
# :updatedAt=>"2022-09-16T12:12:55.454Z",
# :status=>"TRANSFORMED",
# :description=>"test",
# :goals=>
#  [{:Definitions=>[{:text=>"Search for relics", :status=>"TRANSFORMED", :id=>"99999999999999999999999999999999"}],
#    :text=>"Follows Action Plan Appropriately, Worse or Warning Symptoms",
#    :status=>"TRANSFORMED",
#    :id=>"99999999999999999999999999999999"}],
#  :healthConcernDefinitions=>[]}
# Check original (it wasn't changed)
hash

# => {:id=>"11ed35b8e53c442ea210c39d6f24bddf",
# :createdAt=>"2022-09-16T12:12:55.454Z",
# :updatedAt=>"2022-09-16T12:12:55.454Z",
# :status=>"ACTIVE",
# :description=>"test",
# :goals=>
#  [{:Definitions=>[{:text=>"Search for relics", :status=>"INACTIVE", :id=>"11ec1fc4bd36f876963867013cee2799"}],
#    :text=>"Follows Action Plan Appropriately, Worse or Warning Symptoms",
#    :status=>"ACTIVE",
#    :id=>"11e818be2f0157329c76634ee23c1d8f"}],
#  :healthConcernDefinitions=>[]}
# Destructive method
hash.deep_replace!(status: "TRANSFORMED", id: "99999999999999999999999999999999")

# => {:id=>"99999999999999999999999999999999",
# :createdAt=>"2022-09-16T12:12:55.454Z",
# :updatedAt=>"2022-09-16T12:12:55.454Z",
# :status=>"TRANSFORMED",
# :description=>"test",
# :goals=>
#  [{:Definitions=>[{:text=>"Search for relics", :status=>"TRANSFORMED", :id=>"99999999999999999999999999999999"}],
#    :text=>"Follows Action Plan Appropriately, Worse or Warning Symptoms",
#    :status=>"TRANSFORMED",
#    :id=>"99999999999999999999999999999999"}],
#  :healthConcernDefinitions=>[]}
# Check original (it was changed)
hash

# => {:id=>"99999999999999999999999999999999",
# :createdAt=>"2022-09-16T12:12:55.454Z",
# :updatedAt=>"2022-09-16T12:12:55.454Z",
# :status=>"TRANSFORMED",
# :description=>"test",
# :goals=>
#  [{:Definitions=>[{:text=>"Search for relics", :status=>"TRANSFORMED", :id=>"99999999999999999999999999999999"}],
#    :text=>"Follows Action Plan Appropriately, Worse or Warning Symptoms",
#    :status=>"TRANSFORMED",
#    :id=>"99999999999999999999999999999999"}],
#  :healthConcernDefinitions=>[]}
mechnicov
  • 12,025
  • 4
  • 33
  • 56
  • Hi [mechnikov](https://stackoverflow.com/users/10608621/mechnicov)! I understand `deep_transform` method takes in block only or key, or value. It is not suitable for me in my case, I looked for more simpler solution in one row))) But as I can see it is simpler to write custom method. Thanks a lot for helping! – Transfer Sep 17 '22 at 12:31
  • @Transfer you can add your own answer to question with your solution – mechnicov Sep 17 '22 at 12:33
  • 1
    Much nicer. Only additional comment from an efficiency standpoint is that you are still potentially iterating `value` even after it has been replaced. For instance st you replaced `:goal` it would still go through the entire process of for the Array and each object in the Array even though the result would not reflect these changes – engineersmnky Sep 17 '22 at 18:08
1

We could implement a conditional overwrite of a key using an implementation akin to deep_transform_keys.

def deep_overwrite(object, other_h)
  case object
  when Hash
    object.each_with_object({}) do |(key, value), result|
      result[key] = other_h[key] || deep_overwrite(value, other_h)
    end
  when Array
    object.map { |e| deep_overwrite(e,other_h) }
  else
    object
  end
end

While this does not take a block format (as shown in your post), that did not seems to be necessary based on your use case. Instead this will simply overwrite the value of any key (nested at any level) contained in the original (object) that is also contained in other_h.

To call this in your case

original = {:id=>"11ed35b8e53c442ea210c39d6f24bddf",
 :createdAt=>"2022-09-16T12:12:55.454Z",
 :updatedAt=>"2022-09-16T12:12:55.454Z",
 :status=>"ACTIVE",
 :description=>"test",
 :goals=>
  [{:text=>"Follows Action Plan Appropriately, Worse or Warning Symptoms",
    :status=>"ACTIVE",
    :Definitions=>[{:text=>"Search for relics", :status=>"INACTIVE", :id=>"11ec1fc4bd36f876963867013cee2799"}],
    :id=>"11e818be2f0157329c76634ee23c1d8f"}],
 :healthConcernDefinitions=>[]}

result = deep_overwrite(original,{status: 'TRANSFORMED'})
#=> {:id=>"11ed35b8e53c442ea210c39d6f24bddf",
# :createdAt=>"2022-09-16T12:12:55.454Z",
# :updatedAt=>"2022-09-16T12:12:55.454Z",
# :status=>"TRANSFORMED",
# :description=>"test",
# :goals=>
#  [{:text=>"Follows Action Plan Appropriately, Worse or Warning Symptoms",
#    :status=>"TRANSFORMED",
#    :Definitions=>
#     [{:text=>"Search for relics",
#       :status=>"TRANSFORMED",
#       :id=>"11ec1fc4bd36f876963867013cee2799"}],
#    :id=>"11e818be2f0157329c76634ee23c1d8f"}],
# :healthConcernDefinitions=>[]}
engineersmnky
  • 25,495
  • 2
  • 36
  • 52
  • 1
    It is very clever) I like it. Thank you very much, `engineersmnky`. I`m going to implement this solution – Transfer Sep 16 '22 at 20:43