How would I access ProfileService Data Tables

In my game I’m using profile service to save and load Data. My Data Template looks like this

local Data = {
	["PlayerStats"] = {
		["Chakra"] = 0,
		["Strength"] = 0,
		["Health"] = 0,
	},
	["Levels"] = {
		["Level"] = 1,
		["Exp"] = 0,
		["Points"] = 0,
	}
}

If you haven’t noticed already, Instead of storing every single value into one table, I’ve organized them into their own tables. However this brings me to an issue

function PlrDataHandler:Set(player, key, value)
	local profile = GetProfile(player)
	assert(profile.Data[key], string.format("Data does not exist for key %s", key))

	assert(type(profile.Data[key]) == type(value))
	profile.Data[key] = value
	local valueOBJ = player:FindFirstChild(key, true)
	if valueOBJ then
		valueOBJ.Value = value
	end
	
end

This is my method of setting data, I’m getting a key (String) and looking through the data to find the key, (e.g, “Chakra”, “Strength”).
But how can I access the values inside of the tables and edit them?

Maybe a better question to ask is how can I access a certain value in a table. Like if a table has another table inside of it, how can I access the contents of that seconds table? Or is it useless to organize data into tables and just have them layout in only one.

3 Likes

It is useless, because you’re wasting data by saving them as nested tables, instead of just the values themselves. You should only use tables when you need to use them, like saving owned cars and plots.

@Katrist it’s useless for the computer yes. Very handy for human readability though.

@LeCrabCurry You can do

return profile.Data["PlayerStats"]["Health"]

The two strings can be configurable parameters of your get function.

This is what comments are for though, it’s just added complexity having to comb through a table to find the data you really want to retrieve.

function PlrDataHandler:Set(player, key, value) -- key = {"PlayerStats", "Health"}

would making a path like this work? if so how would I incorporate this into my script to find the profile.Data[data1][data2][data3], etc

You would do something like this (index is a more suitable name):

function PlrDataHandler:Set(player, index, key, value)
	--[[
	Index = table name
	Key = value name
	Value = key's new value
	]]
	
	local profile = GetProfile(player)
	
	assert(profile.Data[index][key], string.format("Data does not exist for key %s", key))
	assert(type(profile.Data[index][key]) == type(value))
	
	profile.Data[index][key] = value
	
	local valueOBJ = player:FindFirstChild(key, true)
	
	if valueOBJ then
		valueOBJ.Value = value
	end
end

But what if I want to include more data? such as Inventory = {Item = {Stats = {"A","B","C"}}}
I was thinking of using some sort of loop, for example if the path was {“Inventory”, “Item”, “Stats”, “A”}, then I’d loop through it

function FindPath(path) --  path = {"Inventory", "Item", "Stats", "A"}

local currentPath = profile.Data
for i, v in pairs(path) do
currentPath = currentPath[path[i]]
end
return currentPath -- profile.Data[Inventory]["Item"]["Stats"]["A"]
end

Apologies for not formatting since its just a mockup code I wrote right now, but in theory would this work?

Actually, you can get values nested in tables. You’d have do a deep search, which is possible by modifying the deep clone code in the docs, which I have done here:

local Data = {
	["PlayerStats"] = {
		["Chakra"] = 0,
		["Strength"] = 0,
		["Health"] = 0,
	},
	["Levels"] = {
		["Level"] = 1,
		["Exp"] = 0,
		["Points"] = 0,
	}
}

local function setValue(tbl, key, value)
	for k, v in pairs(tbl) do
		if type(v) == "table" then
			v = setValue(v, key, value)
		end
		
		if k == key then
			tbl[k] = value
		end
	end
end

setValue(Data, "Points", 5) -- Sets the value of Points to 5

This should work, do a test with a print statement after setting a value and see if it updated.

Never said it wasn’t possible. I’m saying that it’s highly impractical to do so, because you’re wasting time combing through tables to reach just 1 value.

Would thus work?
function FindPath(path) – path = {“Inventory”, “Item”, “Stats”, “A”}

local currentPath = profile.Data
for i, v in pairs(path) do
currentPath = currentPath[path[i]]
end
return currentPath -- profile.Data[Inventory]["Item"]["Stats"]["A"]
end

You’re only returning the value in that function, so you wouldn’t be able to mutate that value after. You’re close though.

hm I see, so then what should I do? use table.concat? or would that not apply here

You can just set the value in that function. You could also use a recursive search like @ltsmrsky did.

You said his method is impractical, why is that? Will it take a lot of memory?

I had the same issue but I fixed it by creating a function that lets you access tables like you can access the workspace (PlayerStats.Weapons.Weapon):

GetData({"PlayerStats", "Weapons", "Weapon"}) --// Returns DatastoreProfile.PlayerStats.Weapons.Weapon

If you get what I mean. I’m not home right now so I can’t reply with a script.

Edit:

function SearchTable(Table, directory)
	local Int = 0;
	while Int < #directory do
		Table = Table[directory[Int + 1]];
		Int += 1;
	end;

	return Table;
end;

function RetrieveData(Player, directory)
	local ReturnData = SearchTable(Profiles[Player].Data, directory);

	if ReturnData ~= nil then
		return ReturnData;
	else
		warn("Data not found.");
		return nil;
	end;
end;
1 Like

Yes, it takes a lot more memory and a lot more time to search for the data.

This is similar to the mockup I made, but also wouldn’t the function just return the value of the index we are trying to search for?

Nope, it returns an editable index.

Example:

local Players = game:GetService("Players");
local Client: Player = Players[Player];
local ClientData = DataManager:RetrieveData(Client, {"Data", "PlayerStats"});
	
ClientData.Chakra += 1;
ClientData.Strength += 1;
ClientData.Health += 10;

I’ve used your function, but now I’m getting an error.
ServerScriptService.DataHandler:113: profile does not exist for 2231850740

function GetProfile(plr)
	assert(profiles[plr], string.format("profile does not exist for %s", plr.UserId))
	return(profiles[plr])
end

Also when I print profiles it returns an empty table. Though I’m not sure if this is because of the table combing system I added or it’s something else…

On the scale you’re working at you won’t notice any difference.

Minute (useless) optimizations should be more important than code readability.

It’s not impractical at all.