Rails中merge与deep_merge的差异

Rails 小白的填坑之路。

写在前面

前几天被老大问到merge与deep_merge的区别,一时语塞,试了几个例子,大致get到了两者的区别,但是当时没有记录下来,这里做一下笔记,加深印象。

正文

  • merge用法

    docs中搜索merge,可以看到有10个名为merge的method,这里我们要对比deep_merge,所以选择继承自Hash的ActiveSupport::HashWithIndifferentAccess下的method merge。

    选自ActiveSupport::HashWithdifferentAccess

    merge(hash, &block)

    This method has the same semantics of update, except it does not modify the receiver but rather returns a new hash with indifferent access with the result of the merge.

    此处的merge不会修改当前对象,而是将合并后的结果以一个新的hash表格式返回。

    其实这里的merge就等同于ruby中的merge, 对于两个hash,针对共有的key,默认取后一个hash的key对应的value。

    看个例子:

    h1 = { "a" => 100, "b" => 200 }
    h2 = { "b" => 254, "c" => 300 }
    h1.merge(h2) #=> {"a"=>100, "b"=>254, "c"=>300}
    

    你可以通过block来指定merge的规则:

    h1 = { "a" => 100, "b" => 200 }
    h2 = { "b" => 254, "c" => 300 }
    h1.merge(h2) {|k,v1,v2| v1+v2} #=> {"a"=>100, "b"=>454, "c"=>300}
    h1.merge(h2) {|k,v1,v2| v1} #=> { "a" => 100, "b" => 200 }
    
  • deep_merge用法

    官方给到的解释和例子是这样的:

    deep_merge(other_hash, &block)

    Returns a new hash with self and other_hash merged recursively.

    返回一个新的hash, 将当前对象和其他的hash进行递归merge

    还是以上面的例子为例:

    h1 = { "a" => 100, "b" => 200 }
    h2 = { "b" => 254, "c" => 300 }
    h1.deep_merge(h2) #=> {"a"=>100, "b"=>254, "c"=>300}
    

    同样,你也可以通过block来指定deep_merge的规则:

    h1 = { "a" => 100, "b" => 200 }
    h2 = { "b" => 254, "c" => 300 }
    h1.deep_merge(h2) {|k,v1,v2| v1+v2} #=> {"a"=>100, "b"=>454, "c"=>300}
    h1.deep_merge(h2) {|k,v1,v2| v1} #=> { "a" => 100, "b" => 200 }
    
  • 二者差异

    从上面的例子看,似乎两者没有什么区别。

    在非嵌套的hash表中,merge能做的,deep_merge也能做。

    但是针对nested hash,两者就不一样了。

    看个例子:

    h1 = { a: true, b: { c: [1, 2, 3], x: [1, 2, 3] } }
    h2 = { a: false, b: { x: [3, 4, 5] } }
    h1.merge(h2) #=> {:a=>false, :b=>{:x=>[3, 4, 5]}}
    h1.deep_merge(h2) #=> {:a=>false, :b=>{:c=>[1, 2, 3], :x=>[3, 4, 5]}}
    

    使用merge时,对于相同的key, 它不会去检查对应的value是不是一个hash,就是简单按照merge的规则返回value。

    使用deep_merge,会检查同样的key,对应的value是不是都是hash,如果是, 则对这两个hash再次进行deep_merge,也就是返回{ c: [1, 2, 3], x: [1, 2, 3] }.deep_merge{ x: [3, 4, 5] }的结果,也就是概念中提到的递归merge。

    再看个例子:

    h1 = { a: true, b: { c: [1,2,3], x: { y: 1 } } }
    h2 = { a: false, b: { x: { y: 2, z: 1} } }
    h1.deep_merge(h2) #=> {:a=>false, :b=>{:c=>[1, 2, 3], :x=>{:y=>2, :z=>1}}}
    

    从上面这个例子可以看出,针对共有的key,deep_merge会一直检查对应的value是否都是hash,如果是,则继续对hash进行deep_merge,直到没有hash进行merge为止。

    小结

  • 针对非嵌套的hash:merge和deep_merge并没有什么差异。

  • 针对嵌套的hash: 针对相同的key,merge不会检查,直接处理,根据merge规则返回value,deep_merge则会一直检查对应的value是否都是hash,如果是,则继续对hash进行deep_merge,直到没有hash进行merge为止。

参考

Ruby on Rails API