Is there a better way to serialize data using DataStores?

This is a simple data store script that encodes the data into a JSON format (it auto saves every 30 seconds). The main thing I wan’t to improve is reliability. Right now, the code uses SetAsync(), but would something like IncrementAsync() or UpdateAsync() be better in this situation?

DataStoreScriptDEVFORUM.rbxl (16.4 KB)

Sever Side (Script)

local players = game:GetService("Players")
local httpService = game:GetService("HttpService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local dataStoreService = game:GetService("DataStoreService")
local dataStore = dataStoreService:GetDataStore("DataSerializer_V2")

local clientSerialize = replicatedStorage:FindFirstChild("Events").Serialize

local starterCurrency = 1000

local function serialize(plr)
	local key = "plr#" .. plr.UserId
	
    -- the table below only has one value, but you should be able to add more.
	local data = {
		["Currency"] = plr.leaderstats.Cash.Value
	}
	
	local success, err = pcall(function()
		dataStore:SetAsync(key, httpService:JSONEncode(data))
	end)
	
	if not success then
		warn("Failed to serialize data: " .. tostring(err))
		
		return
	end
end

local function joinGame(plr)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = plr
	
	local currency = Instance.new("IntValue")
	currency.Name = "Cash" -- Just a random name
	currency.Value = starterCurrency
	currency.Parent = leaderstats
	
	-- DataStore
	
	local key = "plr#" .. plr.UserId
	local serializedData
	local deserializedData
	
	local success, err = pcall(function()
		serializedData = dataStore:GetAsync(key)
	end)
	
	if not success then
		warn("Failed to read data: " .. tostring(err))
		
		return
	end
	
	if serializedData then
		deserializedData = httpService:JSONDecode(serializedData)
		
		currency.Value = deserializedData["Currency"]
	else
		serialize(plr)
	end
end

clientSerialize.OnServerEvent:Connect(serialize)
players.PlayerAdded:Connect(joinGame)
players.PlayerRemoving:Connect(serialize)

Client Side (LocalScript)

local replicatedStorage = game:GetService("ReplicatedStorage")

local event = replicatedStorage:WaitForChild("Events").Serialize

local waitTime = 30 -- in seconds

while wait(waitTime) do
	event:FireServer()
end

Something I have had happen is the data does not get serialized when the player leaves (sometimes). I wan’t to make the code as close to perfect as possible, since all my games this method to an extent. Also, does formatting the data in JSON do anything. I use it since it makes it more complex to look at (for security purposes), but I don’t know if it really is something to worry about.

3 Likes

JSON stands for JavaScript Object Notation, and it is specially designed to store information about objects in a human AND machine readable ways - I personally often use it as it is easy to read, so JSON encoding does not make it more secure.

The main advantages of JSON are:

  • It is a very standardised and popular method of storing information about an object, so any major programming language has a library or module to deserialise it (for example HTTPDecode in Roblox’s Lua).
  • It is human readable, and writable - you can edit your objects using Notepad rather than a complex piece of software
  • (Within Roblox) you can use it to store objects created using tables in Roblox’s DataStoreService relatively efficiently

The main disadvantages of JSON are:

  • It is string encoded, meaning that it takes more data to store the same information
  • It is also more computationally expensive to do operations on a JSON file than objects within your script

Other than that, your code looks pretty solid. I would maybe incorporate a 4 retry system i.e. if your attempts to retrieve data fail, keep trying until you either get a success or run out of retries.

I would also recommend different namespaces or at least naming conventions for your top level singletons and services, but this is nit picky and down to personal preference.

3 Likes

The DataStoreService automatically converts data to a JSON string, so no. Also, there isn’t a valid reason from what I can see to expose control of your data update processes to the client, so I’d do something along the lines of:

local last_update = os.time()
local update_interval = 60*3

local function onStep()
	local clock = os.time()
	
	if (os.difftime(clock, last_update) >= update_interval) then
		--save
		last_update = clock
	end
	return
end
RunService.Heartbeat:Connect(onStep)

… on the server instead.

1 Like

Ok thanks! I will add that into all my DataStore systems! I will also remove the JSON encode system.

Ok, I will remove the JSON formatting!

I’m not recommending for or against the JSON system - I personally use :JSONEncode() and :JSONDecode() all the time - although I only save tables, not objects, so that’s better for me as I get finer control over my save system.

Ok, thanks for the advice! I will use this information to help make a secure data store script.

Be careful not to waste time trying to secure the actual datastore - Roblox handles all of that for you

Ok, I will spend more time on actually writing the useful code instead of securing it.

What I meant is that you do have to secure the code, by keeping it on the server etc. but you don’t have to secure the data itself - e.g. don’t bother encrypting it before you :SetASync() or :UpdateASync()

Yes I would never use the client for security.