Help With Saving Table to Datastore

Hey Guys, I have decided just to make my own datastore, but I am encountering an error I don’t quite understand. My Code keeps Erroring out(through a Pcall Function because) with Unable to cast value to function
Here is my code:

Code
local DataStoreService = game:GetService("DataStoreService")
local Bans = DataStoreService:GetDataStore("BanDataStore")
local playerData = DataStoreService:GetDataStore(game.ServerStorage:WaitForChild("datastore").DatastoreName.Value)

game.Players.PlayerAdded:Connect(function(player)
	local key = player.UserId.."-"..player.Name
	local DataGot
	local success, current = pcall(function()
		DataGot = playerData:GetAsync(key)
		return DataGot
	end)
 
	if success then
		print("Stats:", DataGot)
		if DataGot == nil then
			for i, stat in pairs(player.leaderstats:GetChildren())do
				stat.Value = 100	
			end
			return
		else
			for i, stat in pairs(DataGot)do
				print(stat.Value)
			end
		end
		
	end
	
	local success, current = pcall(function()
		local banned = Bans:GetAsync(key)
		if banned == false then
			return
		elseif banned == true then
				player:Kick("You have been Banned!")
		end
	end)
 
	if success then
		print("Player is not Banned")
	end
end)
game.ReplicatedStorage.Events.LoadLeaderstats.OnServerEvent:Connect(function(player,stat,value)
	game.ReplicatedStorage.Events.LoadLeaderstats:FireAllClients(player,stat,value)
		
end)
game.Players.PlayerRemoving:Connect(function(player)
	local sats = player.leaderstats:GetChildren()
	local key = player.UserId.."-"..player.Name
	for i, stat in pairs(sats)do
		local stater = stats[stat.Name]
		stater = stat.Value
		print(stater)
	end
for key, value in pairs(stats) do
	print(key, value)
end
	local success, update = pcall(function()
		return playerData:UpdateAsync(key, stats) --This is Causing the Error
	end)
	if success then
		print(player.Name.." Data Is Saved")
	else
		print(player.Name .. ": Error! Data was not Saved")
		warn(update) --This is what is sending the Error
	end
end)

Pretty much, I am trying to save a table that can be expanded or shortened at anytime.
If you Guys could it help, It would mean a lot!

First, the code is extremely messy, you misspelled “stats” and put “sats” instead.

Instead of your current method, why not try something like this;

--player removing

local DataObject = player.leaderstats -- example
local Data = { }

for i,v in pairs(DataObject:GetChildren()) do
	Data[v.Name] = Data.Value
end
--save data here , simply saving the table

Your current method doesn’t really make a ton of sense, try something like what I have just listed.

So I added your code into a respected place, and it is still throwing the error.
Also sorry it is Messy as I usually code and then after making sure everything works, I clean it up.
Also I misspelled stats to sats just for a quick filler until I clean it up, I would of changed it to leaderStats
Here is the Changed Remove Function:

game.Players.PlayerRemoving:Connect(function(player)
	local Key = player.UserId .."-".. player.Name
	local DataObject = player.leaderstats
	local Data = {}
	for i,v in pairs(DataObject:GetChildren()) do
		Data[v.Name] = Data.Value
	end
	local success, err = pcall(function()
		return playerData:UpdateAsync(Key, Data)
	end)
	if success then
		print(player.Name.." Data Is Saved")
	else
		print(player.Name .. ": Error! Data was not Saved")
		warn(err)
	end
end)

You understand you cannot return a datastore being saved, instead, just return true. This is mainly where your error begins, sorry I didn’t make this clear earlier.

Yes, I just have it Return to make sure if the Datastore Saved using bools. How would I fix that?

btw, this isn’t how you should be using pcall.
pcall returns two things, whether or not it succeeded and the result from the function.
if it’s a success, the result is what was returned from the function, and if it isn’t a success, the result is an error.

so that section of your code can be changed to this:

local success, result = pcall(function()
    return playerData:GetAsync(key)
end)

if success then
    -- use result as you would with "DataGot"
end

If you want a separate thing solely for error, you might want to use xpcall instead.

UpdateAsync accepts two arguments; one is the key you want to save data to, and the second is a function that has its own parameter. This parameter contains the old data from the last previous save. What you want to do is to update your data inside this function and return the updated data.

local Data = -- your data
playerData:UpdateAsync(Key, function(OldData)
   if not OldData or OldData.Id == Data.Id then
      Data.Id = Data.Id + 1
      return Data
  else 
      return nil
  end
end)

The reason why you would want the OldData is so you don’t accidently save something you don’t want to, such as Default data received due to a GetAsync failure. You don’t seem to save “Id” or what “version” you are saving, so it’s redundant using UpdateAsync in this case, though it is certainly more effective at saving data compared to SetAsync.

I would look up how to properly use it here.

1 Like

@Kensizo
This:

for i,v in pairs(DataObject:GetChildren()) do
	Data[v.Name] = Data.Value
end

Wouldn’t work because [v.Name] is not in the table.
__
@besupernow
More experienced developers save datastores in table like this:

local DatastoreTable = {
    Coins = leaderstats.Coins.Value,
    Kills = leaderstats.Kills.Value,
    -- etc.
    }
local success, err = pcall(function()
	playerData:UpdateAsync(Key, function(Data)
        return DatastoreTable
    end)
end)

And then they will load data like this:

if DataGot then
    Coins.Value = DataGot["Coins"]
    Kills.Value = DataGot["Kills"]
    -- etc.
end
1 Like

When you call Data[v.Name] it automatically puts it in the table, as you’re setting what that table is called and what it will hold in the table. It will work, and I currently use this method constantly for my data saving and loading. I do not see the point of using a method like that, it doesn’t seem too different, everyone has a different coding style, and appreciation for coding, but please do not claim something does not work when it does.

2 Likes

That works perfectly fine as its equal to doing:

t["Kills"] = 0
t["Deaths"] = 1
-- etc. etc.

How you save data is not a sign of experience lol.

This is not an effective way to use UpdateAsync, I have posted an effective use case and resource above if needed.

2 Likes

Would I need to use any pcalls?

Correct me if I’m wrong, but I could’ve sworn that if you save data like that, it would error once you load it. But, you learn something every day! (oh no, I swore, don’t tell my teacher)

I did not wrap it in pcalls, which is usually crucial for DataStore calls. So yes, you would need pcalls for sure.

@VegetationBush as long as you have pcalls and are calling these DataStore calls periodically you should be good :+1: It won’t error on GetAsync, don’t worry.

pcall is basically a protected calling method. It allows you to safely run something that might have an error, and then lets you handle the error if there is one.

The reason they’re needed when using Datastore is because it makes an http request, which may fail, and you don’t want to cause an error in your script because of this.

however, I prefer xpcall because it’s easier to handle the error and the error is seperate from the returned result. here’s an example:

local success, result = xpcall(function()
    return 4 -- just an example
end, function(err)
    print(debug.traceback(err)) -- gets the stack trace
end)

now, it prints the error + traceback if it fails. then success would be false and result would be nil. However, if it’s a success, success would be true and result would be 4 (which is what happens in this example)

So I inputed your code in and tested it and tried debugging and all and it won’t even run through the function inside the function for UpdateAsync.
Here is the code:

game.Players.PlayerRemoving:Connect(function(player)
	local Key = player.UserId .."-".. player.Name
	local Data = player.leaderstats:GetChildren()
	local success, result = pcall(function()
		playerData:UpdateAsync(Key, function(OldData)
			if not OldData or OldData.Id == Data.Id then
				Data.Id = Data.Id + 1
				print("Saving")
			    return Data
			else
				print("Data is the Same ID")
			    return OldData
			end
		end)
	end)
	if success then
		print(player.Name.. " Player Data has been Saved")
	else
		print(player.Name " Player data has not been Saved")
		warn("Error Code: "..result )
	end
end)

A little late, but the problem may be that it is not saving in time, which in that case you need to consider adding a BindToClose to determine when the server shuts down, disconnect the PlayerRemoving event, loop through the remaining players list and save data for each player in a seperate thread using coroutines.

local Players = game:GetService("Players")

local Removing(Player)
    local Key = player.UserId .."-".. player.Name
	local Data = player.leaderstats:GetChildren()
	local success, result = pcall(function()
		playerData:UpdateAsync(Key, function(OldData)
			if not OldData or OldData.Id == Data.Id then
				Data.Id = Data.Id + 1
				print("Saving")
			    return Data
			else
				print("Data is the Same ID")
			    return OldData
			end
		end)
	end)
	if success then
		print(player.Name.. " Player Data has been Saved")
	else
		print(player.Name " Player data has not been Saved")
		warn("Error Code: "..result )
	end
end

local Conn = Players.PlayerAdded:Connect(Removing)

game:BindToClose(function()
     Conn:Disconnect()
     for _, Player in pairs(Players:GetPlayers()) do
        coroutine.resume(coroutine.create(function()
              Removing(Player)
         end))
     end
     if game:GetService("RunService"):IsStudio() then
         wait(3)
     else
         wait(30)
     end
end)

I have already fixed it, For some odd reason I didn’t think of doing a for loop and table.insert(). And doing that fixed it

2 Likes