Combining two tables recursively?

I’ve tried messing around with different functions to do this, but it’s melting my brain a little bit. Could anybody help out?

Here are some examples of what I am looking for.

local table1 = {
	a = {
		b = "testing!",
		c = {
			d = false,
		}
	}
}

local table2 = {
	e = "hello world",

	-- Having a nested table with the same key from both tables will combine the contents inside.
	a = {
		f = "hi"
	}
}

local result = {

	a = {
		b = "testing!",
		c = {
			d = false,
		},

--As you see, the there is also an a table in table2, so insert the f value inside of that to the nested table from table1.
		f = "hi",
	}

   e = "hello world"
}

Current code I am working with.

function module.combine(tbl1: {any?}, tbl2: {any}): {any?}
	local function recursive(tbl1, tbl2)
		for key, value in pairs(tbl2) do
			local to_change = value
			
			if type(value) == "table" then
				to_change = recursive({}, value)
			end
			
			tbl1[key] = to_change
		end
		
		return tbl1
	end
	
	local result = recursive(tbl1, tbl2)
	
	return result

I would break it down into two separate calls, one for each table, and only overwrite the key if the key does not exist already.

Suppose a function addEntries with the params
t is the table to be populated, and
from, the table from which t will be populated.

Now, for each table (t1 and t2 as from), iterate over that table, suppose key is a table’s index index and value is the value from your table:
1- If value is a table, then set t[key] to be the return from the addEntries function passing t[key] as t and value as from
2- If t[key] is nil, set t[key] to be value
3- At the end of addEntries return t

Now, call this function passing a blank table as t and a populated table as from

You should end up with something like this:

local function deepMerge(t1, t2)
	local newTable = {}

	local function addEntries(t, from)
		for key, value in from do
			local existsAlready = t[key]

			if type(value) == 'table' then -- point 1
				t[key] = addEntries(existsAlready or {}, value)
			elseif existsAlready == nil then -- point 2
				t[key] = value
			end
		end

		return t -- point 3
	end

	addEntries(newTable, t1)
	addEntries(newTable, t2)

	return newTable
end

Doing this should give you your intended result:
image

You could also easily make this a vararg to combine any number of tables if you wanted to:

local function deepMerge(...)
	local newTable = {}

	local function addEntries(t, from)
		for key, value in from do
			local existsAlready = t[key]

			if type(value) == 'table' then
				t[key] = addEntries(existsAlready or {}, value)
			elseif existsAlready == nil then
				t[key] = value
			end
		end

		return t
	end
	
	for _, from in { ... } do
		addEntries(newTable, from)
	end

	return newTable
end

If you want t1 specifically to be populated, you could also do that by passing t1 as addEntries’s param t:

addEntries(t1, t2) -- replace `t2` with `from` if using the vararg option
2 Likes

I really should go to sleep, I could’ve sworn I sent my draft for this last night but I never did, instead for some reason I deleted it.

I think my original explanation went something like this:
You’re trying to essentially reconcile and merge the tables.

Anyways, here’s a weird method I wrote up in about 10 minutes while sleep deprived ( I still have not slept. )
It may overuse embedded conditionals, can easily be changed to be simpler, but I honestly didn’t even test it, should work afaik. Also provides a simple DeepCopy.

Usage:
ReconcileMerge(Table0: table, Table1: table, Favors: boolean?): table
Favors is optional, it just depicts which source table you prefer to keep regular, non-table, values from. Passing false would be Table1 and is the default, whereas passing true would prefer values from Table0.
and for the DeepCopy it’s just
Deep(table): table

Anyways code before I pass out writing this

local Deep;Deep = function(_table: {any}): {any} -- Small simple strictly typed DeepCopy
	local Copy: {any} = {};
	for Index, Data in pairs(_table) do
		Copy[Index] = if typeof(Data) == "table" then Deep(Data) else Data
	end
	return Copy
end
local ReconcileMerge;ReconcileMerge = function(Table0: {any}, Table1: {any}, Favors: boolean?): {any} -- Table0: table, Table1: table, Favors: true or false (true favors keeping non-table values from Table0, false favors Table1 (false is default))
	Favors = if Favors == nil then false else Favors
	local Copy: {any} = Deep(Table0) -- No need to worry about your initial values. This also deepcopied any Table0 tables that would be missed.
	for Index, Data in pairs(Table1) do
		local T0Value = Table0[Index]
		Copy[Index] = if T0Value == nil then -- If Table0 doesn't have this value,
			if typeof(Data) == "table" then -- And Table1 does and is a table,
				Deep(Data) -- We need to deepcopy and set it.
			else
				Data -- Otherwise, we set it to the current value of Table1
		else -- Instead, if Table0 does have that index then:
			if typeof(T0Value) == "table" then -- If Table0 it's a table value,
				if type(Data) == "table" then -- And Table1 also has a table value here,
					Reconcile(T0Value, Data, Favors) -- We need to Reconcile them again.
				else -- Otherwise, if Table1's value is NOT a Table, we need to:
					if Favors then -- Check our favoring, if true we:
						T0Value -- Use the value from Table0.
					else -- Otherwise, when favor is false (or default)
						Data -- We use the value from Table1.
			else -- If T0Value is not nil and T0Value is ALSO not a table, we again:
				if Favors then -- Check our favoring, if true we:
					T0Value -- Use the value from Table0.
				else -- Otherwise, when favor is false (or default)
					Data -- We use the value from Table1.
	end
	return Copy
end :: (Table0: {any}, Table1: {any}, Favors: boolean?) -> {any}
1 Like

Thank you for your response. I think I’ve put together some test table based off of some of these replies and other sources, and I’ll see if it’s right. I’ll send an update if it seems to be trusty.