How to use ReplaceAll and RuleDelayed with an Association?

This is more of an extended comment rather than an answer. Using this definition:

assoc = <|"a" -> "str1", "b" -> <|"a" -> "str2", "b" -> <||>|>|>
(* <|"a" -> "str1", "b" -> <|"a" -> "str2", "b" -> <||>|>|> *)

when applying this replacement the result was:

assoc /. <|"a" -> v_, "b" -> <||>|> :> v <> "0"
(* <|"a" -> "str1", "b" -> "str2" <> "0"|> *)

whereas the expected result was:

(* <|"a" -> "str1", "b" -> "str20"|> *)

JM suggested using Replace with KeyValuePattern

Using Replace with KeyValuePattern produces the desired result:

Replace[assoc, KeyValuePattern[{"a" -> v_, "b" -> <||>}] :> v <> "0", {1}]
(* <|"a" -> "str1", "b" -> "str20"|> *)

Result is not related to KeyValuePattern

I discovered that KeyValuePattern is not the cure, rather it was using Replace rather than ReplaceAll.

Observe the results below.

Replace[assoc, <|"a" -> v_, "b" -> <||>|> :> v <> "0", {1}]
(* <|"a" -> "str1", "b" -> "str20"|> *)

ReplaceAll[assoc, <|"a" -> v_, "b" -> <||>|> :> v <> "0"]
(* <|"a" -> "str1", "b" -> "str2" <> "0"|> *)

ReplaceAll[assoc,KeyValuePattern[{"a" -> v_, "b" -> <||>}] :> v <> "0"]
(* <|"a" -> "str1", "b" -> "str2" <> "0"|> *)

Independent of the whether we use KeyValuePattern or not, Replace appears to evaluate the result whereas ReplaceAll returns the result without evaluation.

Using List rather than Association

If rather than using an association we use a list, the problem disappears.

listOfRules = {{"a" -> "str1"}, {"b" -> {"a" -> "str2"}}}
(* {{"a" -> "str1"}, {"b" -> {"a" -> "str2"}}} *)

Replace[listOfRules, {"a" -> v_, "b" -> {}} :> v <> "0", Infinity]
(* {"a" -> "str1", "b" -> "str20"} *)

ReplaceAll[listOfRules, {"a" -> v_, "b" -> {}} :> v <> "0"]
(* {"a" -> "str1", "b" -> "str20"} *)

Particular solution

It is interesting than to see that for this particular problem one could convert the association to a list, make the replacement and then convert it back to an association.

assoc /. Association -> List /. {"a" -> v_, "b" -> {}} :> v <> "0" // Association
(* <|"a" -> "str1", "b" -> "str20"|> *)

I don't recommend this (I think J.M's suggestion is the way to go).

Conclusion

It appears that applying ReplaceAll to an association returns the result without evaluation whereas Relace evaluates the result.

This observation is unrelated to the use of KeyValuePattern.


Maybe J.M. doesn't need to write an answer anymore, but I think this answer based on Leonid Shifrin's earlier answer should be recorded.

assoc = <|"a" -> "str1", "b" -> <|"a" -> "str2", "b" -> <||>|>|>;
assoc /. <|"a" -> v_, "b" -> <||>|> :> RuleCondition[v <> "0"]
<|"a" -> "str1", "b" -> "str20"|>