How to automate the indexing of a dictionary for easy use in data stores?

Hey! My name is Mason and I have been scripting on Roblox for quite a while now, but for some reason I am having trouble creating a function to auto-index a pre-set dictionary for use with comparing data stores. If that doesn’t make any sense, I’ll elaborate below.

So here is the data that will be stored inside a server-sided module in-game for each player and then be removed as the player leaves the game (as to not cause a data leak).

r.PlayerDataTemplate = {
	
	leaderstats = {
			
		Money = 1000,
		Rating = 0,
			
	},
	
	Statistics = {
		
		Gameplay = {
			
			MoneyEarned = 0,
			MoneySpent = 0,
			
			HighestRating = 0,
			
		},
		
		Monetization = {
			
			RobuxSpent = 0,
			
		},
		
		Engagement = {
			
			GlobalTimePlayed = 0,
			LongestSession = 0,
			
			Sessions = 0,
			AverageSessionTime = 0,
			
			LastDatePlayed = 0,
			
		},
		
		Profile = {
			
			Username = "",
			DisplayName = "",
			
			UserAge = 0,
			HasPremium = false,
			
		},
		
	},
	
	Settings = {
		
		Audio = true,
		Music = true,
		
	},
	
	Integrity = {
		
		WasCorrupted = false,
		
	},
	
}

The layout of the data can change as much as I want, and instead of automatically setting the player’s data to the above (hardcoding it), I would rather have a function that automatically indexes the current template with gathered indexes from a pre-existing data store dictionary, potentially the exact same layout as that shown above. The only issue is, if they are not the same layout I want to have a way to change only the data already existent (I know how to do this manually, but I like to automate things).

This is an example of how the code is set up, if playerData exists I would like to create an index for each value (since it can be a pretty deep dictionary), and then index the current playerDataTemplate with values. If those values exist, change them.

local success, errormessage = pcall(function()

		local playerDataTemplate = r.PlayerDataTemplate
		local playerData: r.PlayerDataTemplate = playerDS:GetAsync(plr.UserId..gameConfig.SaveVersion)

		-- setup
		if playerData then
			
		else
			newSave = true
		end

		playerDataTemplate.Statistics.Engagement.Sessions += 1

		-- leaderstats
		local leaderFolder: Folder = genUtil.CreateInstance("Folder", "leaderstats", plr)
		local money: IntValue = genUtil.CreateInstance("IntValue", "Money", leaderFolder, {Value = playerDataTemplate.leaderstats.Money})
		local rating: NumberValue = genUtil.CreateInstance("NumberValue", "Rating", leaderFolder, {Value = playerDataTemplate.leaderstats.Rating})

		-- set data
		dataUtil.PlayerData[plr.Name] = playerDataTemplate

		-- update leaderstats
		coroutine.resume(coroutine.create(function()

			while task.wait() do

				money.Value = dataUtil.PlayerData[plr.Name].leaderstats.Money
				rating.Value = dataUtil.PlayerData[plr.Name].leaderstats.Rating

				-- end loop to prevent data leak
				if not game.Players:FindFirstChild(plr.Name) then break end

			end

		end))

	end)

Any solutions? Also, if I did not describe the situation enough please let me know specifics or what you are confused on.

Thanks!

I’m not sure that I 100% understood what you wanted, so my solution below is based on what I assume you want, namely:

If any key in the template are not found in the player’s data, then the player’s data for that key should be set to the default value found in the template. If the value is a dictionary, it should be updated recursively.

local function isDict(toCheck)
	if typeof(toCheck) ~= "table" then 
		return false
	end
	for key, _ in pairs(toCheck) do
		if not tonumber(key) then
			return true
		end
	end
	
	return toCheck == {}
end

local function recursivelyMerge(Primary, Secondary)
	for key, val in pairs(Secondary) do
		if Primary[key] == nil then
			Primary[key] = val
		elseif isDict(val) then
			Primary[key] = recursivelyMerge(Primary[key], val)
		end
	end	
	return Primary
end

local Result = recursivelyMerge(CURRENT, TEMPLATE)

A demonstration of the code can be found in this place file:
Demonstration_MasonJames136_1.rbxl (55.1 KB)