I need to "merge" 2 dynamic objects in C#. All that I've found on stackexchange covered only non-recursive merging. But I am looking to something that does recursive or deep merging, very much the same like jQuery's $.extend(obj1, obj2)
function.
Upon collision of two members, the following rules should apply:
- If the types mismatch, an exception must be thrown and merge is aborted. Exception: obj2 Value maybe null, in this case the value & type of obj1 is used.
- For trivial types (value types + string) obj1 values are always prefered
- For non-trivial types, the following rules are applied:
IEnumerable
&IEnumberables<T>
are simply merged (maybe.Concat()
? )IDictionary
&IDictionary<TKey,TValue>
are merged; obj1 keys have precedence upon collisionExpando
&Expando[]
types must be merged recursively, whereas Expando[] will always have same-type elements only- One can assume there are no Expando objects within Collections (IEnumerabe & IDictionary)
- All other types can be discarded and need not be present in the resulting dynamic object
Here is an example of a possible merge:
dynamic DefaultConfig = new {
BlacklistedDomains = new string[] { "domain1.com" },
ExternalConfigFile = "blacklist.txt",
UseSockets = new[] {
new { IP = "127.0.0.1", Port = "80"},
new { IP = "127.0.0.2", Port = "8080" }
}
};
dynamic UserSpecifiedConfig = new {
BlacklistedDomain = new string[] { "example1.com" },
ExternalConfigFile = "C:\\my_blacklist.txt"
};
var result = Merge (UserSpecifiedConfig, DefaultConfig);
// result should now be equal to:
var result_equal = new {
BlacklistedDomains = new string[] { "domain1.com", "example1.com" },
ExternalConfigFile = "C:\\my_blacklist.txt",
UseSockets = new[] {
new { IP = "127.0.0.1", Port = "80"},
new { IP = "127.0.0.2", Port = "8080" }
}
};
Any ideas how to do this?
Right, this is a bit longwinded but have a look. it's an implementation using Reflection.Emit.
Open issue for me is how to implement a ToString() override so that you can do a string comparison. Are these values coming from a config file or something? If they are in JSON Format you could do worse than use a JsonSerializer, I think. Depends on what you want.
You could use the Expando Object to get rid of the Reflection.Emit nonsense as well, at the bottom of the loop:
I can't see a way around the messy code for parsing the original object tree though, not immediately. I'd like to hear some other opinions on this. The DLR is relatively new ground for me.
This works for me, but I'm sure it could be given some love and attention and look better. It does not include your type-check but that would be rather trivial to add. So while this is not a perfect answer, I hope it can get you closer to a solution.
Subsequent calls to DynamicIntoExpando(...) will keep appending and overwriting new and existing values to the existing Source structure. You can call it as many times as you need to. The function MergeDynamic() illustrates how two dynamics are merged into one ExpandoObject.
The code basically iterates over the dynamic value, checks the type, and merge appropriately and recursively to any depth.
I wrapped it in a helper class for my own purposes.