Returning the Path in a Dictionary instead of Value

I’ve tried Googling this question in numerous ways, asking in Discords, and still haven’t found a solution, so to the Dev Forums, I come!

In my last game, I stored Player Data in Objects within the Players, similar to how leaderstats are handled. When I wanted to modify the Player’s Data, I’d sometimes need to use a For-Loop to get certain Objects which were nested inside of dictionaries. The following is an example of this

		local directorySplit = string.split(directory, ".")
		local directoryPath = player
		for _, path in ipairs(directorySplit) do
			directoryPath = directoryPath:FindFirstChild(path)
		end

Then with directoryPath I could set the Value of it to modify the Player’s Data by using directoryPath .Value = 5 for example.

Although now I’ve changed my ways of working with Player Data and even switched to the popular Player Profiles Module. Since Data is now stored inside a dictionary, I no longer use Objects which have the Value property.

I’d like to accomplish being able to modify a Player’s Data, similarly to my previous method.

It’s simple to modify Data that isn’t inside of a Dictionary because I can do something like profile.Data[path] = 10, but when the path is trying to find a dictionary property, such as path = AutoTaps.Time, I’m forced to use a For-Loop because profile.Data[AutoTaps.Time] would return nil.

So I’ve created a For-Loop to handle this

local function getPath(data: table, paths: string)
    for _, path in ipairs(paths:split(".")) do
        data = data[path]
    end
    return data
end

This function will return the Data, so for instance 10 instead of the actual path to the data, which would allow me to modify the data. So if I’m looking for player.Data.AutoTaps.Time I want to get the actual path, not the value.

The only ‘solution’ I’ve thought of was the following, although it certainly isn’t a good one:

        local paths = path:split(".")
        if #paths == 1 then
            profile.Data[paths[1]] = value
        elseif #paths == 2 then
            profile.Data[paths[1]][paths[2]] = value
        end

This accomplishes what I need to, but it’s required within the function where I modify the Player’s data, which isn’t what I desired. I will also need to add more elseif's to accommodate further nested tables.

I’m not sure if I understood it correctly, but here’s what I thought.

If it’s multiple nested tables, wouldn’t you just need to index the last table, then give the key to the value

local function getPath(data: table, paths: string, value:string)
	for _, path in ipairs(paths:split(".")) do
		data = data[path]
	end
	return data, data[value] -- Returns the table, and its value
end

or if you want, you can ignore the last index

local function getPath(data: table, paths: string)
	local split = paths:split(".")
	local lastIndex = #split
	for i, path in ipairs(split) do
		if i == lastIndex then break end
		data = data[path]
	end
	return data
end

I’m sure there is a better way than checking for last index, but I don’t know how.

Hey, thanks for the response! I should’ve included further context, especially what the data looks like.

Types.ProfileTemplate = {
    Taps = 0:: number,
    AutoTap = {
        Enabled = false,
        Time = 0,
    }:: AutoTap,
}

That is how data would be structured when passed to the function. So unless I’m misunderstanding or while testing your suggestions, those wouldn’t / don’t work in this scenario.

I’m not entirely sure what your problem is, maybe I misunderstood but I think you can do profile.Data.AutoTap.Time, and you don’t need to get the path as that literally is the path. However, if you for some reason really wanted to do paths by using strings you could do:

local profile = {
	Data = {
		Taps = 0:: number,
		AutoTap = {
			Enabled = false,
			Time = 0,
		}:: AutoTap,
	}
}

local function stringPath(t,s) -- this gets a path from a string
	local paths = string.split(s,".")
	local current = t
	for i,v in ipairs(paths) do
		current = current[v]
	end
	return current
end

local examplePath = "Data.AutoTap.Time" -- path must be a string

print(stringPath(profile, examplePath)) -- put the table and then the path starting from inside the table

If I want to modify the Player’s Data, I’ll use a Function like the following:

local function SetData(player: Player, path: string, value: any)
   local profile = Profiles[player.UserId]
   profile[path] = value
end

I’m unable to use the direct path that you suggested profile.Data.AutoTap.Time because that’s not possible. What I previously would do is in the example above, using profile[path] = value as that works for non-nested paths such as Taps, but doesn’t work for nested-paths such as AutoTap.Enabled.

By looking at your example, it looks like it will not return me an actual modifiable path, but the value of the path, which is what I displayed was my issue in my original post. Although I could be mistaken.

This works for changing values:

local function stringPath(player:Player,s, changeToMake) -- leave changeToMake blank when calling the function to return the value, otherwise put it to whatever want it to change to
	local paths = string.split(s,".")
local t =Profiles[player.UserId]
	local current = t
	for i,v in ipairs(paths) do
		current = current[v]
	end
	if changeToMake == nil then
		return current
	else
		current = changeToMake
	end
end

local examplePath = "Data.AutoTap.Time"

stringPath(game.Players.Lit_skillzYT, examplePath, 5) -- changes the value to 5