Thoughts on this recursive table verification algorithm?

The Algorithm
The first argument t is the table to be verified, and mt are the values to set if they are not in t.

function VerifyTableValues(t, mt)
	for index, object in pairs(mt) do
		t[index] = t[index] or mt[index]
		if type(t[index]) == "table" then
			VerifyTableValues(t, mt[index])
		end
	end
end

Thoughts behind the algorithm and how it works:
When creating player DataStores, I was searching for a solution to ensure a player’s DataStore table(s) will have the default values needed for the blueprint of the DataStore’s layout. For example, what if we need to add additional fields to a DataStore and guarantee every single player will have that value in their DataStore table, regardless of when the player joined and had their key created?

The algorithm will check every layer of table nesting and only assign a new value to an indexed key if it does not already exist in the table t. If the value already exists, it will use the already stored value. If a new value is assigned that is a table or there is a table already existing under that key, the function is called again recursively to go to the next layer of nesting to repeat the process.

Use-Case:

Say we have a DataStore key which looks like,

local Player_Data = {
	Experience = 12345,
	Level = 12,
}

Every player in the game has this table during the release of the game. Later, we add a GamePass to add an experience multiplier which each player needs to save. We would then set a blueprint table to hold all default values we expect this DataStore to hold, such as:

local Default_Exp_Data = {
	Experience = 0,
	Level = 1,
	Multiplier = 1,
}

Call VerifyTableValues with our Player_Data as t and Default_Exp_Data as mt

VerifyTableValues(Player_Data, Default_Exp_Data)
print(Player_Data.Experience) --> 12345
print(Player_Data.Level) --> 12
print(Player_Data.Multiplier) --> 1

The original data we had is still in our Player_Data table, but we now have the additional Multiplier field which needed to be added.

If you have any critique, improvements, or gotchas to point out please let me know! This was something I couldn’t find a solution for myself and I want to verify this will not cause data loss or any other bugs I am not aware of at the moment. Feel free to use the algorithm yourself if you find it useful.

Your code has two issues:

  • First, if you were to store boolean values, the t[index] = t[index] or mt[index] would always overwrite false values on t if the default was true on mt. Instead, check if t[index] == nil, and then assign the default to t.

  • Second, you are calling the function recursively on t and mt[index], so while you would begin to explore a sub-table of mt, the defaults would still be assigned to t, not to the sub-table of t indexed with index. To fix this, call recursively on t[index] and mt[index]. I would recommend checking if t and mt are tables before calling recursively.

  • Another thing you can consider is to remove fields that are no longer necessary from t when they are not in mt, this avoids keeping old useless information.