Three Useful Functions for Working with Tables

Hello, everyone!

I’d like to share with you three useful functions that are specifically designed for working with tables and could help you with DataStore saving and loading.

Let’s get started!

DeconstructValueObjects (ValueBase Objects → Table)

Purpose: A basic function that recursively converts the instance tree rooted at Ancestor into a table.

Function:

function DeconstructValueObjects(Ancestor: Instance): {any}
	local Converted = {}
	for _, Object in ipairs(Ancestor:GetChildren()) do
		if Object:IsA("Folder") or Object:IsA("Configuration") then
			Converted[Object.Name] = DeconstructValueObjects(Object)
		elseif Object:IsA("ValueBase") then
			Converted[Object.Name] = (Object :: ValueBase & {Value: any}).Value
		end
	end
	return Converted
end

Parameters:

  • Ancestor: An Instance object representing the root of the instance tree to convert. The function will recursively convert the ValueBase objects within Ancestor and its descendants.

Returns:

  • table: A dictionary containing the values of the ValueBase objects within the given Ancestor instance and its descendants, organized into a hierarchy of sub-tables based on the structure of the instance tree. Folder and Configuration instances serve as sub-table keys in the resulting table.

Code Example:

image

-- Assuming that we have a folder/configuration named `Stats` in the player object containing: 
-- Coins, TotalPlayTime, and a sub-configuration named `Sub-Stats` that contains: `IsAlive`, and `IsAFK` Boolean Objects.

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

local StatsTable = DeconstructValueObjects(Stats)
print(StatsTable)

-- The output should be like:
--[[
    ["Coins"] = 20,
    ["TotalPlayTime"] = 10,
    ["Sub-Stats"] = {
        ["IsAlive"] = true,
        ["IsAFK"] = false
    }
]]


TableToValueObjects (Table → ValueBase Objects)

Purpose: Converts a table into a hierarchy of ValueBase instances, with the structure of the hierarchy reflecting the structure of the input table.
The resulting hierarchy will be rooted at an instance of the specified type and parented to the specified Instance object if provided.

Function:

function TableToValueObjects(Table: any, RootObject: (string | Instance)?, Parent: Instance?, DefaultGroupingObject: string?, Processed: any?): Instance
	if Processed and Processed[Table] then
		return nil
	end

	local Root
	local DGroupingClassName = DefaultGroupingObject or ("Folder")
	local Processed = (Processed or {})
	Processed[Table] = true

	if typeof(RootObject) == "Instance" then
		Root = RootObject
	elseif type(RootObject) == "string" then
		local Success, Obj = pcall(function() return Instance.new(RootObject) end)
		if Success then Root = Obj else Root = Instance.new("Folder") end
	else
		Root = Instance.new("Folder")
	end

	local ObjectsMapping = {
		["Ray"] = "RayValue",
		["integer"] = "IntValue",
		["boolean"] = "BoolValue",
		["number"] = "NumberValue",
		["string"] = "StringValue",
		["Color3"] = "Color3Value",
		["CFrame"] = "CFrameValue",
		["Instance"] = "ObjectValue",
		["Vector3"] = "Vector3Value",
		["BrickColor"] = "BrickColorValue",
	}

	for Key, Value in pairs(Table) do
		local ValueType = typeof(Value)
		local Name = (typeof(Key) ~= "Instance" and Key) or (tostring(Key))
		if ValueType ~= "table" then
			local Obj = Instance.new(ObjectsMapping[ValueType])
			Obj.Name = Name
			Obj.Value = Value
			Obj.Parent = Root
		else
			local Children = TableToValueObjects(Value, DGroupingClassName, Root, DGroupingClassName, Processed)
			if Children then Children.Name = Name end
		end
	end
	Root.Parent = Parent
	return Root
end

Parameters:

  • Table: A table containing the values to convert into ValueBase instances.
  • RootObject (Optional): A string or an Instance object representing the type of the root instance to create for the resulting hierarchy. If not specified, a Folder instance will be used as the root.
  • Parent (Optional): An Instance object to parent the resulting hierarchy to. If not specified, the hierarchy will be left unparented.
  • DefaultGroupingObject (Optional): A string representing the type of instance to use as the root of hierarchies created for table values in the input table. If not specified, a Folder instance will be used.
  • Processed (Optional): A table used by the function itself to track tables that have already been converted to avoid infinite recursion.

Returns:

  • Instance: An object representing the root of the resulting hierarchy of ValueBase instances.

Code Example:

local Players = game:GetService("Players")
local DefaultSettings = {
	["GuiTheme"] = "Dark",
	["CursorIcon"] = "Dot",
	["TimeFormat"] = 24,
	["OwnedHouses"] = {
		workspace.House_1
	},
	["Graphics"] = {
		["DepthOfField"] = true,
		["Blur"] = false,
	}
}

Players.PlayerAdded:Connect(function(Player)
	local Settings = TableToValueObjects(DefaultSettings, "Configuration", Player, "Configuration")
	Settings.Name = "Settings"
	-- ...
end)

image



Equals ((Table, Table) → boolean)

Purpose: Compares two tables for equality. If Recursive is true, the function will recursively compare the values of any nested tables it encounters. If UseMetamethodEquality is true, the function will use the __eq metamethod of the tables if they have one, instead of comparing the tables’ contents.

Function:

function Equals(Table_1: {[any]: any}, Table_2: {[any]: any}, Recursive: boolean?, UseMetamethodEquality: boolean?, Cache: any?): boolean
	--| Early exit statements:
	if rawequal(Table_1, Table_2) then return true end	--| Checking if the tables are the same (i.e., stored in the same memory location)
	if #Table_1 ~= #Table_2 then return false end		--| Tables are not equal if they have different lengths.
	if next(Table_1) == nil and next(Table_2) == nil then return true end	--| Checking if the tables are both empty.
	if UseMetamethodEquality then
		local MT1 = getmetatable(Table_1::any)
		local MT2 = getmetatable(Table_2::any)
		if MT1 and MT2 and MT1.__eq and MT2.__eq then
			return Table_1 == Table_2
		end
	end

	--| Cache is used to prevent infinit recursion. Ignored if there is no `Recursive` is false or nil.
	local Cache = (Cache ~=  nil and Cache) or (Recursive and {})::any
	if Recursive then
		if Cache[Table_1] and Cache[Table_2] then
			return true
		end
		Cache[Table_1] = true
		Cache[Table_2] = true
	end

	--| Comparing Table_1 to Table_2:
	for Key, Value in pairs(Table_1) do
		if type(Value) ~= "table" then
			if Table_2[Key] ~= Value then
				return false
			end
		else
			if not Recursive then continue end
			if type(Table_2[Key]) ~= "table" then return false end
			if Cache[Value] and Cache[Table_2[Key]] then return true end
			if not Equals(Value, Table_2[Key], true, UseMetamethodEquality, Cache) then return false end
		end
	end

	--| Comparing Table_2 to Table_1:
	for Key, Value in pairs(Table_2) do
		if type(Value) ~= "table" then
			if Table_1[Key] ~= Value then
				return false
			end
		else
			if not Recursive then continue end
			if type(Table_1[Key]) ~= "table" then return false end
			if Cache[Value] and Cache[Table_1[Key]] then return true end
			if not Equals(Value, Table_1[Key], true, UseMetamethodEquality, Cache) then return false end
		end
	end
	return true
end

Parameters:

  • Table_1: The first table to compare.
  • Table_2: The second table to compare.
  • Recursive (Optional): A boolean value indicating whether the function should recursively compare the values of any nested tables it encounters. If not specified, the default value is false.
  • UseMetamethodEquality (Optional): A boolean value indicating whether the function should use the __eq metamethod of the tables if they have one, instead of comparing the tables’ contents. If not specified, the default value is false.
  • Cache (Optional): A table used by the function itself to track tables that have already been compared to prevent infinite recursion (e.g. tables that have __index to themself).

Returns:

  • boolean: A boolean value indicating whether the provided tables are equal or not.

Code Example:

local Old = {
    Stats = {
        Health = 100,
        Armor = 50,
        Coins = 1000,
        Kills = 50
    },

    Settings = {
        Graphics = {
            Blur = true,
            DepthOfField = false,
        }
    }
}

local New = {
    Stats = {
        Armor = 50,
        Health = 100,
        Kills = 50,
        Coins = 1000
    },
    Settings = {
        Graphics = {
            Blur = true,
            DepthOfField = false,
        }
    }
}

print(Equals(Old, New, true))  -- Output: true

-- Changing the Blur setting:
New.Settings.Graphics.Blur = false

print(Equals(Old, New))        -- Output: true (the Recursive argument is set to false by default)
print(Equals(Old, New, true))  -- Output: false (Blur is not equal to the one inside the old table)

I hope these functions will be helpful to you! :wink:

6 Likes