Data Serializer 2.0 | Module for saving Parts, Models, Value Objects and more!

Hello everyone! It took me hours upon researching how to store unique object identifiers (if Instance == Instance then, but as strings) into a Data Store, until I realized it was impossible, unfortunately. My goal was to be able to recreate the same object that was serialized and sent to a DataStore. The only workaround and alternative was to utilize GUIDs.

This is a module that serializes a folder with its descendants into the form of a table to be sent to a Data Store.

Data Serializer 2.0: Marketplace | GitHub | API-Documentation

If you don’t want to manipulate GUID’s and prefer a simpler folder-based data store, I recommend using the Legacy version of this module instead.


Why use this?

  • It has minimal control over data! Unlike some other community modules, this one will not manage when to save, update, or remove requests to Data Stores on its own.

  • Capable of saving Models, MeshParts, ObjectValues, Scripts, Attributes and more!

  • The serialization structure is organized and easy to manipulate with plugins!

How to use (Setup and Limitations)

Module Structure:

DataSerializer \ -- ModuleScript
    LoadData | -- ModuleScript
    SaveData | -- ModuleScript

Initial Setup

  • A folder named “PresetPlayerData” must be present under the ServerStorage. This will be used as the default folder for each new player.
  • If you want to customize PresetPlayerData and it has GUID attributes, remove all GUIDs with the command bar in Studio using this code below.

Remove GUIDs:

local HttpService = game:GetService("HttpService")
local ServerStorage = game:GetService("ServerStorage")
local PresetPlayerData = ServerStorage:FindFirstChild("PresetPlayerData")

if not PresetPlayerData then
    PresetPlayerData = Instance.new("Folder")
    PresetPlayerData.Name = "PresetPlayerData"
    PresetPlayerData.Parent = ServerStorage
end

local function setUniqueId(object)
    local aName = "GUID"
    if object:GetAttribute(aName) then
        object:SetAttribute(aName, nil)
    end
end

setUniqueId(PresetPlayerData)
for i, v in pairs(PresetPlayerData:GetDescendants()) do
    setUniqueId(v)
end

This is the part where you want to start customizing PresetPlayerData, it can be achieved by adding Value objects and folders within the preset folder. More information about setting up PresetPlayerData can be found here.

Changing Values

The Data Serializer 2.0 version allows any Instance to be stored under PresetPlayerData, alongside their attributes.

This script features unrestricted saving, meaning that storing any instance type is possible. This includes: Models, Beams, Full NPCs, Scripts, LocalScripts, MeshParts, Highlight, and everything else featured in the “Insert Object” menu. However, it is recommended PresetPlayerData should be treated the same as the Data Serializer Legacy version.

Instances supported:

  • Everything

Attribute data types supported:

  • All data types are supported.

Limitations

If an object’s property is referencing an Instance that will not be saved, it may be referenced to a cloned version of the object instead.

Examples of objects with a property referencing an Instance:

  • SurfaceGui.Adornee
  • WeldConstraint.Part0
  • Motor6D.Part0
  • Beam.Attachment1

For example, if SurfaceGui and SurfaceGui.Adornee are not within the same Model, SurfaceGui.Adornee will be cloned to be set as the new Adornee for the next time the Player joins the experience. This practice of setting the Adronee property is not good because it is referencing to an object of the game rather than the player. To make the Adornee officially an object of the Player, Adornee should be cloned, then set it as the new Adornee.

-- example of making a new adornee.
local SurfaceGui

local newAdornee = SurfaceGui.Adornee:Clone()
newAdornee.Parent = SurfaceGui.Adornee.Parent

SurfaceGui.Adornee = newAdornee

MeshParts and Scripts

MeshParts, Scripts, and SurfaceAppearance can persist, but they need to be present within the ServerStorage service.

  • For scripts to save, the name must match with the one in ServerStorage.
  • MeshParts and SurfaceAppearances must have matching copies containing the same Properties as the one in ServerStorage. Name will not matter.

Deprecated Objects

Deprecated objects are objects that are no longer being maintained by Roblox engineers. These objects are supported in this script, but certain objects may not fully persist, meanining that there are certain properties of deprecated objects that may not save.

Post Setup

Now that PresetPlayerData has been fully customized, it will need the GUID attributes once again.

Using the Command Bar

PresetPlayerData will be cloned to the Player, however the script is going to think the whole folder is new everytime, therefore it will create a number of PresetPlayerData folders within itself depending on the number of times the player rejoins the game. To prevent this, an attribute named “GUID” must be set on PresetPlayerData and its descendants. This is possible using the command bar.

Setting GUIDs during a live game will not be necessary because using DataStore:Update() or DataStore:CleanUpdate() will set the GUIDs on objects automatically. A live game is when the client is playing the game.

Set GUIDs:

local serverStorage = game:GetService("ServerStorage")
local HttpService = game:GetService("HttpService")
local preset = serverStorage:FindFirstChild("PresetPlayerData")

local function checkMatchId(folder)
	local function checkObj(scannedObj, ParaObject)
		if scannedObj ~= ParaObject then
			local guid = scannedObj:GetAttribute("GUID")
			if guid then
				if guid == ParaObject:GetAttribute("GUID") then
					ParaObject:SetAttribute("GUID", HttpService:GenerateGUID(false))
					checkMatchId(ParaObject)
				end
			end
		end
	end
	
	checkObj(preset, folder)
	for i, v in pairs(preset:GetDescendants()) do
		checkObj(v, folder)
	end
end

local function setGuid(folder)
	if not folder:GetAttribute("GUID") then
		folder:SetAttribute("GUID", HttpService:GenerateGUID(false))
		checkMatchId(folder)
	end
end

setGuid(preset)
for i, v in pairs(preset:GetDescendants()) do
	setGuid(v)
end
Other uses

Other Uses

Retrieving Data Folder

After DataStore:Get() is called, it will set an attribute named “DSLoaded” to the Player containing the name of the Folder that is parented to the Player. This example shows how to retrieve the PlayerData folder from the Player:

local Players = game:GetService("Players")

Players.PlayerAdded:Connect(function(Player)
    --check if data store loaded
    if not Player:GetAttribute("DSLoaded") then
        Player:GetAttributeChangedSignal("DSLoaded"):Wait()
    end
    
    --find Player Data folder
    local PlayerData = Player:FindFirstChild(Player:GetAttribute("DSLoaded"))
end)

Making a Save Icon

While the Player’s data is saving, Player:GetAttribute("IsSaving") will be set to true, then false when it finishes saving data on the Player.

How to use this in code:

local Players = game:GetService("Players")
local Player = Players.LocalPlayer

Player:GetAttributeChangedSignal("IsSaving"):Connect(function()
    local isSaving = Player:GetAttribute("IsSaving")
    if isSaving then
        -- do something with icon
    else
        -- stop icon
    end
end)
Prebuilt Script

This script and the DataSerializer module must both be parented to ServerScriptService.

--[Made by Jozeni00]--
--settings
local DataSettings = {
	--{DATA}--
	--Any changes made below are susceptible to a clean data wipe, or revert data to its previous.
	["Name"] = "DS_Test2V0-0-0"; --DataStore name for the entire game.
	["Key"] = "Plr_"; --prefix for key. Example: "Player_" is used for "Player_123456".

	--{FEATURES}--
	["AutoSave"] = true; --set to true to enable auto saving.
	["SaveTime"] = 1; --time (in minutes) how often it should automatically save.

	["UseStudioScope"] = true; --set to true to use a different Scope for Studio only.
	["DevName"] = "DEV/DS_Test2V0-0-0"; --Name of the Data Store for Studio if UseStudioScope is true.
	["DevKey"] = "Dev_"; --Key of the Data Store for Studio, if UseStudioScope is true.
}

--scripts
local ServerScriptService = game:GetService("ServerScriptService")
local dataModule = ServerScriptService:FindFirstChild("DataSerializer") -- DataSerializer Module Script.
local DataSerializer = require(dataModule)

--players
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

--set scope
if DataSettings.UseStudioScope then
	if RunService:IsStudio() then
		DataSettings.Name = DataSettings.DevName
		DataSettings.Key = DataSettings.DevKey
	end
end

local DataStore = DataSerializer:GetStore(DataSettings.Name)

--on entered
function onPlayerEntered(Player)
	local key = DataSettings.Key .. Player.UserId

	--player data
	local PlayerData = DataStore:Get(Player, key, {Player.UserId})

	if DataStore and DataSettings.AutoSave then
		local isGame = true
		local plrRemove = nil
		if DataSettings.SaveTime < 1 then
			DataSettings.SaveTime = 1
		end
		local saveTimer = DataSettings.SaveTime * 60

		plrRemove = Players.PlayerRemoving:Connect(function(plr)
			if plr == Player then
				isGame = false
			end
		end)

		while Player and isGame do
			task.wait(saveTimer)

			--update
			DataStore:Update(Player, key)
		end

		if plrRemove and plrRemove.Connected then
			plrRemove:Disconnect()
		end
	end
end

--on removing
function onPlayerRemoving(Player)
	local key = DataSettings.Key .. Player.UserId
	DataStore:CleanUpdate(Player, key)
end

for i, v in pairs(Players:GetPlayers()) do
	if v:IsA("Player") then
		local onEnter = coroutine.wrap(function()
			onPlayerEntered(v)
		end)
		onEnter()
	end
end

--events
Players.PlayerAdded:Connect(onPlayerEntered)
Players.PlayerRemoving:Connect(onPlayerRemoving)

game:BindToClose(function()
	print("Closing...")
	for i, v in pairs(Players:GetPlayers()) do
		if v:IsA("Player") then
			v:Kick()
		end
	end
	task.wait(3)
	print("Name:", DataSettings.Name)
end)
--[Made by Jozeni00]--
What if this module receives an update?

Do I need to update this module?

If you only care about the basic needs, then no. Although, this repository will still be maintained to support newer object classes Roblox may add in the near future.

Showcase:

-or-
Place FILE:
DS_2-0_Place.rbxl (81.1 KB)


Update Log

Update 1.1

Changed the way Enums are serialized to a more simplified manner. This new update should only be used for newer games because the saving structure was slightly changed.

Feedback is appreciated, thank you!

13 Likes