How to save IntValue within a StringValue (DataStoreService)

I am currently working on a system where players can use multiple different characters that can be levelled up. When they unlock the character, the character itself is a StringValue inside of a folder in the player, I also add IntValues inside the StringValue, the current problem is that I only know how to save the StringValue and not the IntValues inside the StringValue. And every time the player rejoins, the IntValues inside the StringValue are no longer there.

-- How I set up the StringValues and IntValues
local vTable = gameinfomodule.Characters[character]
		
local characterValue = Instance.new("StringValue", plr.Inventory)
characterValue.Name = character
		
local level = Instance.new("IntValue", characterValue)
level.Name = "Level"
level.Value = 1
		
local Xp = Instance.new("IntValue", level)
Xp.Name = "XP"
Xp.Value = 0
		
local MaxXp = Instance.new("IntValue", level)
MaxXp.Name = "MaxXP"
MaxXp.Value = 20
		
local dmg = Instance.new("IntValue", characterValue)
dmg.Name = "Damage"
dmg.Value = vTable.BaseDmg
		
local Hp = Instance.new("IntValue", characterValue)
Hp.Name = "HP"
Hp.Value = vTable.BaseHP 
		
local Special = Instance.new("IntValue", characterValue)
Special.Name = "SpecialDmg"
Special.Value = vTable.BaseSpecial 
-- DataStore that I use
local datastoreservice = game:GetService("DataStoreService")
local datastore = datastoreservice:GetDataStore("DataStore")

game.Players.PlayerAdded:Connect(function(player)
	local inventory = Instance.new("Folder")
	inventory.Name = "Inventory"
	inventory.Parent = player
	
	local data

	local success,errorMsg = pcall(function()
		data = datastore:GetAsync(player.UserId)
	end)
	
	if data.inventory then
		for i, v in pairs(data.inventory) do
			local val = Instance.new("StringValue")
			val.Name = v
			val.Parent = inventory
		end
	end
end)

game.Players.PlayerRemoving:Connect(function(player)
	local data = {}
    data.inventory = {}
	
	for i, v in pairs(player.Inventory:GetChildren()) do
		table.insert(data.inventory, v.Name)
	end

	local success,errorMsg = pcall(function()
		datastore:SetAsync(player.UserId,data)
	end)

	if errorMsg then
		print("Error found while trying to save data (are roblox datastore servers down?)"..errorMsg)
	end
end)

you are trying to insert a string into a table that doesn’t exist
it’s easy to fix

local data = {
	inventory = {}
}

everything else should be fine

That isn’t the issue, I am trying to save IntValues within a StringValue. The StringValue saves just fine, please read the full post before giving me a reply. (I took some parts of my script out, that is why the table isn’t there.)

to save everything inside your inventory folder:

local items = folder
local data = { items = {} }

for index, item : StringValue in ipairs(items:GetChildren()) do
	data.items[item.Name] = {}

	for _, int : IntValue in ipairs(item:GetChildren()) do
		data[int.Name] = int.Value
	end
end

how your data will look like (example)

local items = {
	["Item1"] = {
		["IntValue1"] = 1
		...
	}
}
1 Like

Here’s a basic, albeit inefficient, serializer and deserializer for StringValues, BoolValues, and NumberValues. If you want to save more complex datatypes like CFrames, Vector3s, Color3s, etc. you’ll need something more advanced than this since those can’t be saved to DataStore, but this should serve your purposes just fine.

type SerializedValueBase = {Name: string, Value: string | number | boolean, Children: {SerializedValueBase}}

local function serializeValueBaseRecursive(vb: StringValue | NumberValue | BoolValue): SerializedValueBase
	local t: SerializedValueBase = {
		Name = vb.Name;
		Value = vb.Value;
		Children = {}
	}
	
	for _, child: Instance in pairs(vb:GetChildren()) do
		if child:IsA("StringValue") or child:IsA("NumberValue") or child:IsA("BoolValue") then
			table.insert(t.Children, serializeValueBaseRecursive(child))
		end
	end
	
	return t
end

local function deserializeValueBaseRecursive(serialized: SerializedValueBase): Instance
	local instance: Instance
	local name: string, value: any, children: {SerializedValueBase} = serialized.Name, serialized.Value, serialized.Children
	
	if typeof(value) == "string" then
		instance = Instance.new("StringValue")
	elseif typeof(value) == "number" then
		instance = Instance.new("NumberValue")
	elseif typeof(value) == "boolean" then
		instance = Instance.new("BoolValue")
	end
	
	instance.Value = value
	
	for _, childData: SerializedValueBase in pairs(children) do
		deserializeValueBaseRecursive(childData).Parent = instance
	end
	
	return instance
end
local players = game:GetService("Players")
local dataStores = game:GetService("DataStoreService")
local dataStore = dataStores:GetDataStore("DataStore")

local protectedCall = pcall
local typeCheck = type

local function deserializeData(object, data)
	for _, value in ipairs(data) do
		local instance = Instance.new(value[2])
		instance.Name = value[1]
		instance.Value = value[3]
		instance.Parent = object:FindFirstChild(value[4], true)
	end
end

players.PlayerAdded:Connect(function(player)
	local inventory = Instance.new("Folder")
	inventory.Name = "Inventory"
	inventory.Parent = player

	local success, result = protectedCall(function()
		return dataStore:GetAsync(player.UserId)
	end)
	
	if success then
		if result then
			if typeCheck(result) == "table" then
				deserializeData(player, result)
			end
		end
	else
		warn(result)
	end
end)
	
local function serializeDataRecursive(object, data)
	for index, stat in ipairs(object:GetChildren()) do
		if stat:IsA("ValueBase") then
			table.insert(data, {stat.Name, stat.ClassName, stat.Value, stat.Parent.Name})
			if stat:FindFirstChildWhichIsA("ValueBase") then
				serializeDataRecursive(stat, data)
			end
		end
	end
	return data
end
	
players.PlayerRemoving:Connect(function(player)
	local data = serializeDataRecursive(player.Inventory, {})
	
	local success, result = protectedCall(function()
		return dataStore:SetAsync(player.UserId, data)
	end)
	
	if success then
		if result then
			print(result)
		end
	else
		warn(result)
	end
end)

game:BindToClose(function()
	for _, player in ipairs(players:GetPlayers()) do
		local data = serializeDataRecursive(player.Inventory, {})

		local success, result = protectedCall(function()
			return dataStore:SetAsync(player.UserId, data)
		end)

		if success then
			if result then
				print(result)
			end
		else
			warn(result)
		end
	end
end)

image

I’ve tested this with some random value instances (including the ones you provided) and it works, it stores the type of value instance, the name, its value and its parent (where it should be placed).

One other thing which is unrelated, in the way you’re setting up stats, change this.

local characterValue = Instance.new("StringValue", plr.Inventory)
characterValue.Name = character

To this.

local characterValue = Instance.new("StringValue", plr.Inventory)
characterValue.Name = character.Name

It’s a “StringValue” instance not an “ObjectValue” instance (unless “character” is some reference to a string value, in which case you can ignore this).

1 Like