Converter Module: Instance ↔ Table | Save Instances to DataStores and More!

Overview

When browsing the DevForum, I frequently come across posts asking how to save ObjectValues, CFrames, Instances, etc. to DataStores. I went ahead and made a public module to help streamline the serialization process since it can be a little bit troubling for beginners who are trying to figure out how to save things to DataStores but only end up with errors when they try to save incompatible datatypes. This can be a great tool for productive developers as well since it has many different use-cases, ranging from a dedicated data management utility all the way to something like Studio plugins.

There are a couple other existing modules on the Marketplace that achieve converting instance data for DataStores. However, I found that they had a mixture of missing features, ran into compatibility errors, and/or didn’t offer much in terms of customization. I also wanted to make something tailored for the Insert Object+ plugin as well as something that is fully compatible with the ModuleScripts from the Instance to ModuleScript Converter plugin.

Creator Marketplace Link

Roblox Creator Marketplace

Converter Module: Instance ↔ Table
https://www.roblox.com/library/9768577563/Converter-Module

Features

  • Supports the DataStore/DataStore2 format.
  • Supports object variables.
  • Supports Workspace and Lighting properties.
  • Can be used without HTTP Service.
  • Many customizable settings available.
  • Complete backwards compatibility.
  • Full attribute support.
  • Supports ALL data types (CFrame, String, Vector3, UDim2, Ray, Faces, etc.)
  • Compatible with the ModuleScripts created from the Instance to ModuleScript Converter plugin.

Use Good DataStore Practices

You should only store objects in DataStores if they are unique objects. I do NOT recommend storing more than what is absolutely necessary in DataStores.

If your object is not unique, instead of saving an object to a DataStore, save a string (the name of the object) and find that model/object in your preferred storage location using that string when loading the data.

It should be OK to save individual or small groups of instances like accessories, clothing, values, and parts since they take up little save data. But for objects with many different instances inside of them like models, you should only save them to DataStores if the player is using something like a building sandbox where they have the ability to drastically modify the model.

How To’s

How to Convert a Table/Instance

Converting to Table:

local ConverterModule = require(9768577563) --Define and require the Module.
local InstanceToConvert = game.Workspace.Baseplate --Define an instance.
local ConvertedTable = ConverterModule:Convert(InstanceToConvert) --Create a variable for the converted data.
print(ConvertedTable) --Do stuff with the converted data.

Converting to Instance:

local ConverterModule = require(9768577563) --Define and require the Module.
local TableToConvert = {a = "hello", b = "world", c = 123} --Define a table.
local ConvertedInstance = ConverterModule:Convert(TableToConvert) --Create a variable for the converted data.
if ConvertedInstance then
	ConvertedInstance.Parent = game.Workspace --Do stuff with the instance.
end

How to Save an Instance to a DataStore
local Converter = require(9768577563) --Require the module using the assetId and set it to the variable Converter
function SaveInstance(Player) --We'll make a function with an empty parameter, which we will recieve as the Player.
	if not Player then return end --We need to make sure the Player exists. If the player doesn't we return.
	local DataStoreService = game:GetService("DataStoreService") --Define the DSS
	local StoreKey = DataStoreService:GetDataStore("SavedInstances") --Define a key within the DataStore. In this example, we'll call it SavedInstances.
	local UserId = tostring(Player.UserId) --We'll fetch the Player's UserId but we need it in string so we will use tostring().
	if UserId == "nil" then return end --We need to make sure the UserId exists. If it doesn't, we return.
	local InstanceToSave = game:GetService("Workspace"):FindFirstChild("Baseplate") --Define an instance, which we will convert then save.
	local ConvertedData = Converter:ConvertToTable(InstanceToSave) --We'll call a function of the module. Since we want a table, we'll use the :ConvertToTable() function and supply the instance we want to convert.
	if not typeof(ConvertedData) == "table" then warn("Error with converted data.") return end --We need to make sure the data is a table, since DataStores save tables of information. If the Converter fails, it will return nil, which is not a table. 
	local SuccessfulSave, ErrorMessage = pcall(function() --We need to wrap a function with pcall to make sure the script doesn't crash if there is an error setting the data.
		StoreKey:SetAsync(UserId, ConvertedData) --We'll set the user's data to the instance data. 
		--[[ This is how the hierarchy would look like:
				• DataStoreService 
					• SavedInstances 
						• Player's UserId
							• User's Data
		]]
	end)
	if SuccessfulSave == false then --If there was an error,
		warn(tostring(ErrorMessage)) --print the error.
	else --If there were no issues,
		print(UserId .."'s saved data:") --print the saved data.
		print(ConvertedData)
	end
end

game:GetService("Players").PlayerRemoving:Connect(SaveInstance) --Down here, we bind the SaveInstance function to the leaving player whenever a the player is removed from the Players service (when they leave, are kicked, or disconnect from the server).

How to Use Custom Settings

In order to use custom settings, we need to create a dictionary.

local CustomSettings = {}

To change the conversion settings to be different than the default settings, we just need to define them in the dictionary. [Note: If there’s a default setting that we are ok with, we don’t have to specify them in the dictionary.]

CustomSettings.IncludeProperties = false
CustomSettings.IncludeObjectVariables = false
CustomSettings.PrintCleanupAmount = true

Now we just need to supply the dictionary as the second parameter to the conversion function.

ConverterModule:Convert(InstanceOrTable, CustomSettings)

Use Case Examples

Save and Load Instances

Saving models to DataStores has never been easier! In the example above, with just the click of a button I can easily load or save all of the properties of my in-game furniture! You can visit the place here (place is also uncopylocked).

Click to view the DataStore code from the place above
local Converter = require(9768577563)
local DSS = game:GetService("DataStoreService")
local Housing = DSS:GetDataStore("Furniture")
function DoDataStuff(Player, Method)
	if not Player then return false end
	local PlayerID = Player.UserId
	local HouseVal = Player:FindFirstChild("ClaimedHouse")
	if not HouseVal or typeof(PlayerID) ~= "number" then return false end
	if not HouseVal.Value then return false, "You don't own a house!" end
	local House = HouseVal.Value
	local FurnitureHolder = House:FindFirstChild("FurnitureHolder")
	local AlignmentPart = House.PrimaryPart
	if not AlignmentPart then return false, "An error occurred. House has no alignment part." end
	if not FurnitureHolder then return false, "An error occurred. House has no furniture model." end
	if Method == "Save" then
		local ConvertedTable = Converter:Convert(FurnitureHolder)
		if typeof(ConvertedTable) == "table" then
			local Success,Fail = pcall(function()
				Housing:SetAsync(tostring(PlayerID), ConvertedTable)
			end)
			return Success, Fail
		else
			return false, "An error occured saving instance data."
		end
	elseif Method == "Load" then
		local InstanceTable
		local Success,Fail = pcall(function()
			InstanceTable = Housing:GetAsync(tostring(PlayerID))
		end)
		if Success then
			if typeof(InstanceTable) == "table" then
				if GetDictionaryLength(InstanceTable) > 0 then
					local ConvertedInstance = Converter:Convert(InstanceTable)
					if typeof(ConvertedInstance) == "Instance" then
						FurnitureHolder:ClearAllChildren()
						local NewAlignmentPart = ConvertedInstance:FindFirstChild("AlignmentPart")
						for i,v in pairs(ConvertedInstance:GetChildren()) do
							v.Parent = FurnitureHolder
						end
						if AlignmentPart and NewAlignmentPart then
							FurnitureHolder.PrimaryPart = NewAlignmentPart
							FurnitureHolder:PivotTo(AlignmentPart.CFrame)
						else
							warn("An error occurred. Either the house or the save data has no alignment part. Furniture was cleared.")
							FurnitureHolder:ClearAllChildren()
							return false, "An error occurred."
						end
						return true
					else
						warn("An error occured converting " ..tostring(PlayerID).."'s instance data.")
						return false
					end
				end
			end
		else
			warn("An error occured loading data for " ..tostring(PlayerID).. ": " ..tostring(Fail))
		end
	end
end

game.ReplicatedStorage.LoadMyData.OnServerEvent:Connect(function(Player) return DoDataStuff(Player,"Load") end)
game.ReplicatedStorage.SaveMyData.OnServerInvoke = function(Player) return DoDataStuff(Player,"Save") end

This is only a snippet of the code. To view the rest of the code, you can edit the uncopylocked place.

Easy Player Data Management

My personal favorite use of this module is to navigate and manage Player Data using folders. I manage everything in folders and attributes since attributes have built in editors and is a lot easier to use than navigating huge tables of data in the script editor. I just need to convert the folder into a table while loading the player’s data. It also leaves no room for user error if you have multiple people in Team Create handing the data. Typos or mistakes that would otherwise break the script are no longer present since the converter translates it directly into a table format.

Click to view conversion code
local Converter = require(9768577563)
local PlayerDataFolder = game:GetService("ServerStorage"):WaitForChild("DefaultPlayerData")
local DefaultPlayerData = Converter:Convert(PlayerDataFolder, {IncludeProperties = false})

Here’s the .rbxm file containing the Player Data folders shown in the example:

DefaultPlayerData.rbxm (1.2 KB)

Click to view the converted DefaultPlayerData table (Ready for DataStores!)
local DefaultPlayerData = {
	["AscensionCharacterInfo"] =  {
		["Hat"] = "GreenEarsElf",
		["Hat2"] = "MagicBeanTwirl",
		["Hat3"] = "RAPTORHELM",
		["SkinColor"] =  {
			["1"] =  {
				["B"] = 1,
				["G"] = 0.0470588244497776,
				["R"] = 0.0470588244497776,
				["Time"] = 0
			},
			["2"] =  {
				["B"] = 0.01568627543747425,
				["G"] = 0,
				["R"] = 1,
				["Time"] = 0.1231281161308289
			},
			["3"] =  {
				["B"] = 1,
				["G"] = 0.3333333432674408,
				["R"] = 0,
				["Time"] = 0.4342761933803558
			},
			["4"] =  {
				["B"] = 0.3333333432674408,
				["G"] = 1,
				["R"] = 0.5333333611488342,
				["Time"] = 0.5690515637397766
			},
			["5"] =  {
				["B"] = 0.01568627543747425,
				["G"] = 0,
				["R"] = 1,
				["Time"] = 0.7537437677383423
			},
			["6"] =  {
				["B"] = 0.8666666746139526,
				["G"] = 0,
				["R"] = 1,
				["Time"] = 0.9750415682792664
			},
			["7"] =  {
				["B"] = 0.3725490272045135,
				["G"] = 0.3294117748737335,
				["R"] = 1,
				["Time"] = 1
			}
		},
		["Walkspeed"] =  {
			["Max"] = 75,
			["Min"] = 40
		}
	},
	["CharacterOptions"] =  {
		["HairColor"] =  {
			["1"] =  {
				["B"] = 255,
				["G"] = 81,
				["R"] = 0,
				["Scale"] = 255
			},
			["2"] =  {
				["B"] = 113,
				["G"] = 37,
				["R"] = 0,
				["Scale"] = 255
			},
			["3"] =  {
				["B"] = 127,
				["G"] = 0,
				["R"] = 0,
				["Scale"] = 255
			}
		},
		["Hats"] =  {
			["1"] = "BlueElfEars",
			["2"] = "RedElfEars",
			["3"] = "LittleEars"
		}
	},
	["CharacterStats"] =  {
		["Faction"] = "Neutral",
		["LostArmDuringCutscene"] = false,
		["MagicClass"] = "Arcane",
		["Nickname"] = "",
		["SkinColor"] = ""
	},
	["PlayerStats"] =  {
		["Deaths"] =  {
			["CreatureDeaths"] =  {
				["Birds"] = 0,
				["Deer"] = 0,
				["Ghosts"] = 0
			},
			["PlayerDeaths"] = 0
		},
		["Kills"] =  {
			["CreatureKills"] =  {
				["Birds"] = 0,
				["Deer"] = 0,
				["Ghosts"] = 0
			},
			["PlayerKills"] = 0
		},
		["Level"] =  {
			["CurrentLevel"] = 1,
			["MaxLevels"] =  {
				["Max"] = 100,
				["Min"] = 1
			},
			["XP"] =  {
				["CreatureHunt"] =  {
					["XPLevel"] = 0
				},
				["PvP"] =  {
					["XPLevel"] = 0
				},
				["StoryMode"] =  {
					["XPLevel"] = 0
				},
				["TotalXPLevel"] = 0
			}
		},
		["Money"] = 0,
		["Playtime"] = 0,
		["PurchasedDLCPass"] = false,
		["UserId"] = ""
	},
	["PurchasedHats"] =  {
	}
}

Serialize Any Instance

InCommand with Converter Module

By printing the converted table into the output, you can easily copy & paste the table into a script or any other text editor. The above example uses the InCommand plugin.

Click to view the plugin command
for i,v in pairs (game.Selection:Get()) do
    if v then
        local ConvertedTable = require(9768577563):ConvertToTable(v, {['ShowHTTPWarning'] = false, ["PrintCompletionTime"] = false})
        print(tostring(v.Name).. ":")
        print(ConvertedTable)
    end
end

Documentation

Function Parameters

Both conversion functions allow up to two parameters to be supplied.
The first parameter is the data (instance or table).
The second optional parameter is the list of override settings (dictionary).

ConverterModule:Convert( Data : Instance or Table, Settings : Dictionary )

Returning Variables

All functions will return a variable (the converted instance/table or nil).

Functions that convert an Instance to a Table will return a second bool variable if the table size is within the DataStore size limit.

Click to view example.
local ConverterModule = require(9768577563) 
local InstanceToConvert = game.Workspace.Baseplate
local ConvertedTable, TableIsWithinLimits = ConverterModule:Convert(InstanceToConvert)
if TableIsWithinLimits == true then
	print("Converted table meets DataStore requirements. :D")
elseif TableIsWithinLimits == false then
	print("Converted table does not meet DataStore requirements. :(")
end

Available Functions

For convenience, I included multiple functions with different names that achieve the same thing.

Click to view a list of all available functions.

    Converting to Instance

    :Convert() :ConvertToInstance() :ConvertToObject() :TableToObject() :DictionaryToObject() :ArrayToObject()

    Converting to Table

    :Convert() :ConvertToTable() :ConvertToDictionary() :ConvertToArray() :ObjectToArray() :ObjectToTable() :ObjectToDictionary()

Available Settings

This is a list of all available settings and their defaults. If you want to use the converter with settings different than the default, simply provide your own dictionary as the second parameter when calling the function! :smiley:

local DefaultSettings = {

	--Property Settings--
	IncludeProperties = true,
	OnlySaveUniqueProperties = true, --This setting greatly reduces table size. When enabled, the converted table will only include properties if they are different than the default.
	OnlySaveWritableProperties = true, --If Studio has a property marked as "Read Only", it will not be included when converting to table. Greatly reduces table size. If you are missing important properties, try setting this to false.
	ExcludedProperties = {"ExcludedProperty1","ExcludedProperty2","ExcludedProperty3","etc."},
	SpecificExcludedProperties = {
		--FORMAT: ["ClassName"] = {"ExcludedProperty1","ExcludedProperty2","ExcludedProperty3","etc."},
		--EXAMPLE: ["Part"] = {"LeftSurfaceInput", "TopSurfaceInput","BottomSurfaceInput", "RightSurfaceInput", "FrontSurfaceInput", "BackSurfaceInput"}, 
	},
	IgnoreAttachmentWorldProperties = true, --99% of the time, you won't need the attachment World properties over CFrame. Enabling this option greatly helps with accurate attachment creation.
	FetchLatestPropertiesOnline = false, --If set to true, HTTPService.HttpEnabled needs to be enabled. If set to true and HTTPService.HttpEnabled is not enabled, it will revert to false. Using this setting requires a response from the setup.rbxcdn.com servers, so any response time will also add to your conversion time. (Typically 0-3 seconds but varies by server.)
	ShowHTTPWarning = true, --If true, a warning will appear in the output if you try to fetch the latest properties while not having HTTP Service enabled.
	IncludeAttributes = true, --Includes the attributes during conversion. [NOTE: When converting into an instance, if a property can't be found, it will automatically be applied as an attribute so I recommend keeping this setting enabled.]
	IncludeTags = true, --Includes any CollectionService tags during conversion.
	ExcludedTags = {"ExcludedTag1", "ExcludedTag2", "ExcludedTag3", "etc."}, --(Case Sensitive)
	IncludeObjectVariables = true, --Objects are located using FindFirstChild so make sure your items within the same directory have UNIQUE names! [NOTE: Object variables with DataStoreFriendly DISABLED will reference the ORIGINAL variable so welds and other attachments will not work.]
	Color3Scale = 255, --99% of the time, you should leave this setting alone. I don't suggest changing this setting unless you are using a custom instance table that has a different scale. 255 = Color3.fromRGB | 1 = Color3.new. This NEEDS to match the same scale as the instance table's formatted scale for accurate color reproduction. 
	EnumType = "Enum", --"Enum", "String", or "Int". If set to String, only the EnumItem.Name will be saved. I recommend keeping this on Enum.
	
	--DataStore Compatability Settings--
	DataStoreFriendly = true, --You can disable this if you don't need the table for DataStores. (The table size will be slightly smaller.)
	PrintDataStoreApproximateSize = false, --When converted with DataStoreFriendly enabled, the module will print the approximate size of the table in KB/MB.
	
	--InstanceSettings-- (These settings only apply when converting from Table to Instance)
	DefaultInstanceType = "Folder", --If ClassName is not specified in the table, it will convert the table into this type of instance. 
	CarryOverSurfaceAppearances = false, --When converting to INSTANCE, this will carry over ALL POSSIBLE SurfaceAppearance variations for that specific MeshPart if it's found in-game. Command bar/plugins can create SurfaceAppearances from scratch, so keep as false if using a plugin. This option is only needed during runtime scripts. This will increase the cost on performance (loops through every single item in game!), so only enable if you need it!
	--NOTE: CarryOverSurfaceAppearances will currently NOT function until a Roblox bug gets resolved.
	IncludeSurfaceAppearancesOverride = false, --When converting to TABLE, include the SurfaceAppearance if the current security level cannot write the properties for SurfaceAppearance objects. (If you aren't using a plugin, do you still want the SurfaceAppearance data, even though SurfaceAppearances can't be modified during runtime scripts?)
	AutoConvertMeshParts = true, --Automatically converts any MeshParts into a normal Part with a SpecialMesh object inside ONLY IF the original mesh can't be found in-game. Scripts cannot make MeshParts due to Roblox not providing support for runtime collision rendering. Hopefully Roblox will address this issue in the future so I can remove the need for this setting. If you use this converter outside of runtime (like with a plugin), set this property to false since MeshParts can still be created in edit mode.
	AutoSmoothParts = true, --This automatically makes all part surfaces smooth if there isn't a SurfaceInput specified in the instance table (removes the inlets/stud textures).
	
	--Output Settings--
	PrintCompatibilityErrors = true, --If there are any issues with a specific datatype or an instance class, it will print a list of those errors.
	PrintObjectVariableErrors = true, --If an error occurs when setting an object property, a warning will be put in the output. (Typically this will happen if the object is nil or if there are similar names under the same directory.)
	PrintCompletionTime = false, --Prints the amount of time the conversion takes to complete. This does not take into account any server hangups (For example: when converting into a massive directory, it doesn't take into account the delayed time it takes the server to create the instances from Instance.new).
	PrintCleanupAmount = false, --When converting a table into an instance, the source table needs to be cloned. The cleanup function will clean up the copy after using it for conversion. If this setting is enabled, it will print how much background memory in MB was cleaned up (approximate).

}

Note: Dictionary keys and all string values are case-sensitive.

Considerations

  • With DataStore tables that contain string formatted paths for object variables, all object variables are located with :FindFirstChild(). Each time it searches a directory, it might end up searching the wrong sub-directory if the directory contains duplicate names. Ensure your items don't have duplicate names within the same parent instance if you are utilizing object variables.
    Click to view a script that removes duplicate names.
    local AlwaysActive = false --false = only on server start up | true = always running. Setting this to true could be very intensive on performance.
    local Directories = {game.Workspace, game.ServerStorage, game.ReplicatedStorage}
    
    function RemoveDupeNames(Par)
    	local DupeNames = {}
    	if #Par:GetChildren() > 1 then
    		local CurrentNames = {}
    		local Children = Par:GetChildren()
    		for i = 1, #Children do
    			if table.find(CurrentNames, Children[i].Name) and Children[i].Name then
    				table.insert(DupeNames,Children[i].Name)
    			end
    			table.insert(CurrentNames, Children[i].Name)
    		end
    	end
    	local Count = {}
    	for i,v in pairs(Par:GetChildren()) do
    		if typeof(v.Name) ~= "string" then return end
    		if table.find(DupeNames, v.Name) then
    			local OldName = v.Name
    			if not Count[OldName] then
    				Count[OldName] = 1
    			end
    			v.Name = OldName.. tostring(Count[v.Name])
    			Count[OldName] += 1
    		end 
    	end
    end
    
    function DoForEveryParent(Par)
    	if not typeof(Par) == "Instance" then return end
    	RemoveDupeNames(Par)
    	for i,v in pairs(Par:GetChildren()) do
    		if AlwaysActive == true then
    			v:GetPropertyChangedSignal("Name"):connect(function() RemoveDupeNames(Par) end)
    		end
    		DoForEveryParent(v)
    	end
    	if AlwaysActive == true then
    		Par.ChildAdded:connect(function() RemoveDupeNames(Par) end)
    	end
    end
    
    for _,Directory in pairs(Directories) do
    	DoForEveryParent(Directory)
    end
    
  • This module runs many nested loops so it can be stressful on performance (depends on the directory size). I would not recommend using the converter multiple times inside of a big loop.

    Note: I’ve done some trial runs to stress test the server with varying amounts of descendants. Keep the following in mind when trying to balance server performance. According to a few trial runs:

    • Massive directories (20,000+ Instances) consistently took at least 3 seconds.
    • Medium directories (1,000 Instances) typically took less than 600 milliseconds.
    • Smaller directories (200 Instances) typically took less than 70 milliseconds.
  • Converting to instances use Instance.new(). Creating, reparenting, and destroying multiple instances will always affect server performance. The more instances you create during the conversion, the more it will affect performance. If you want to maintain good server performance, you should use the To Instance conversion on large tables as infrequent as possible.
  • DataStores still have a limit of 4 MB per key. Customized models with thousands of descendants or many unique properties can easily reach this limit so I suggest separating large directories into multiple groups before conversion.
  • You should never allow this module to be accessed by the client. Always keep it in a secure location such as ServerStorage or ServerScriptService. Never store this module in ReplicatedStorage, Workspace, or any other place the client can access. If the client is allowed access to this module, they could theoretically use it with an external program to copy all of your building data. (This is already possible but if you give them access to this module, it makes it easier to accomplish.)
  • MeshParts are not fully supported until Roblox allows computation of mesh collisions during runtime. When converting from Table to Instance, if a MeshPart cannot be found in the place and AutoConvertMeshParts setting is enabled, any MeshParts will be converted into a normal Part with a SpecialMesh. This means the collisions will reflect the Part collisions instead of the Mesh collisions. To avoid this, ensure you have that specific MeshPart located somewhere in your published/saved place.
  • Unions are NOT supported. Unions don’t carry any readable part data. It would be possible to convert unions if there was access to the :Seperate() function. Unfortunately, that function is currently only accessible from plugins.

Disclaimers

  • If something goes wrong during the conversion, the conversion will return nil so make sure you are checking for a conversion result. I’ve tried to compile as many warnings as possible so that you’d be able to know when something goes wrong and how to fix it.
  • This is a public module which will receive occasional updates. Bugfixes and future feature patches may take additional time depending on the scope since I need to verify the integrity of the changes before updating the module.
Click to view changelog

Changelog History

June 3rd, 2022 | v1.0.0
  • DevForum Launch & Public Release
June 6th, 2022 | v1.1.0
  • Added Enum.Value support. To use, set EnumType to “Int”.
  • Reworked MeshPart support. MeshParts are now supported if there is an existing mesh with the same MeshId in game. If there isn’t one, it will follow the rules of AutoConvertMeshParts. Special thanks to @Manachron for the ingenious workaround.
  • Added support for a few different Services. (Workspace, Lighting, StarterPlayer, TestService, SoundService, Chat)
June 9th, 2022 | v1.1.1
  • The warning message for a delayed HTTP response now includes the (rounded) time in seconds.
  • Fixed a HTTP 403 Forbidden error by changing the domain path. (Previous address to verify HTTPEnabled had an incompatible path.)
  • During each session, the module now saves property fetches between conversions. Previous behavior was that it would fetch a new set of properties during every single conversion, which didn’t help performance when calling the convert function multiple times. Also when fetching from the online dataset, it sent way too many requests. It will now only refetch properties if the OverrideSettings contain a new list of ExcludedProperties.
August 31st, 2022 | v1.1.2
  • Added property and attribute support for the new Font DataType.
  • Added additional failsafes for unsupported attribute types. (ObjectValues, etc.)
  • When converting to an object, unsupported attributes will now be converted to a seperate object (DefaultInstanceType) with its attributes in a table format. The new object will be inside the object that could not have its attributes set.
  • If you don’t want objects made for unsupported attributes, you can revert to v1.1.1 (listed in the VersionIDList script).
November 10th, 2022 | v1.1.3
  • Fixed a CustomPhysicalProperties issue when creating instances.
  • Updated the OfflineAPI to the latest Studio API. (Previous OfflineAPI was from March 27th.)
November 11th, 2022 | v1.1.4
  • Fixed an issue when creating new MeshParts where properties were copied from the source part instead of the data table.
  • Added a cache for MeshParts.
November 12th, 2022 | v1.1.5
  • Added an IgnoreAttachmentWorldProperties conversion option. (Default is set to true.)
  • Critical Fix: Even though the module did not have proper support for Union conversions, after a recent Studio update, attempting to convert a UnionOperation or a model with a UnionOperation caused a critical error causing full-on crashes. Unions are now automatically converted as Parts instead.
November 16th, 2022 | v1.1.6
  • Added an OnlySaveWritableProperties conversion setting. (Default: true)
    Note: If you end up with certain properties missing from your tables, try setting this to false.
  • Added CarryOverSurfaceAppearances and IncludeSurfaceAppearancesOverride conversion settings for MeshParts. (Default: false)
    Note: CarryOverSurfaceAppearances will currently not function until a Roblox bug gets resolved.
  • SurfaceAppearance objects are now automatically converted into DefaultInstanceType until Roblox provides an update that allows SurfaceAppearances to be changed during runtime.
  • Fixed an issue where properties and attributes weren’t being reset for MeshParts prior to applying properties from the table.
December 4th, 2022 | v1.1.7
  • Added Tag support. Use the IncludeTags setting (default: true) with the ExcludedTags setting (array) to specify tags you don’t want to include during conversion.
  • Fixed an issue where tags weren’t being reset for MeshParts prior to applying properties from the table.
December 14th, 2022 | v1.1.8
  • Bugfix for FontFace properties not being correctly set with DataStoreFriendly enabled.
  • Fixed an issue with OnlySaveWritableProperties where write-only property fetches were replacing precached read+write fetches.
April 6th, 2023 | v1.1.9
  • Small fix for an infinite loop timeout error when trying to convert and encode an instance/object property that has been parented to nil. Objects parented to nil should now return as a blank string.
48 Likes

sounds cool but too confusing for my tiny brain lol

4 Likes

Not at all! :smiley: The module is designed to be used with ease by all likes of developers.

The module can be used to create variables within just a single line of code:
local Converted = require(9768577563):Convert(WhateverYouWantToConvert)

Check out the How To’s as well as some of the use cases. There are also a handful of in-depth DataStore tutorials across the DevForum and YouTube to get you started.

4 Likes

Wow! I’ll keep this in mind :slight_smile:

Maybe add a Enum option to use Enum.Value rather than Enum.Name. It might save space (if someone uses this to save data).

1 Like

This is exactly what I’ve been looking and it works very well, I am slightly disappointed about the mesh parts not being compatible but I understand it is out of your control, I think I may be able to find a workaround myself for part meshes but if you can add an option for settings to search the children of an instance for mesh parts with the same id it can clone instead of creating a new instance so we can load instances with mesh parts it would be a helpful feature.

1 Like

Thanks for the suggestion! That’s a great idea for saving space.

The reason I initially chose to only have an option for the Name is because of the potential of Enum lists receiving changes in future updates. If you save the value of the Enum and some of those values get renamed, removed, or reordered in an update, you might end up with the wrong or possibly non-existent Enum when loading old save data. I was unsure if Roblox will always respect the previous Enum values when making changes. :man_shrugging: I would at least hope so. I’ll definitely add an option for it in the next version though.

Does this work with Services? Like for example getting the properties of LightingService?

1 Like

Thanks for the awesome feedback! @savio14562 @Manachron @Bitterman1021

Note: When using :Convert() or :ConvertToTable() on a Service, only the Service will be converted. Any descendants will not be converted. Using the :Convert() or :ConvertToInstance() with a service table will overwrite the Service’s properties with the table data.

1 Like

Oh yeah!!! :tada::tada::tada::tada: Thank you so much! I’ve been really needing this to easily get the properties of LightingService for a lighting module I’ve been making.

EDIT: Hey so I was trying it out and when printing LightingService’s properties it just prints this:

table: 0x9a6c6d0f0baa3139

Here is my code:

local ConverterModule = require(9768577563)
local InstanceToConvert = game.Lighting
local ConvertedTable = ConverterModule:Convert(InstanceToConvert)
print(ConvertedTable)

Sorry for the trouble.

1 Like

No trouble at all! :smiley: I think your output might be in log mode if I had to wager a guess. Log mode uses the memory address in the form of a hex code instead of the table contents. I’ve ran into that issue in the past when Studio updated and reverted the output settings.

Click the 3 dots in the corner → Uncheck Log Mode

Screenshot_3720

3 Likes

Just tried that and It worked perfectly! Thank you so much!

1 Like

@HeyWhatsHisFace Can I have source code for reading on mobile? thanks

Yoo I would definitely try this out! It will be very useful for my building game. By the way, any idea how to save properties of parts as well using this module? (material, colour, position, transparency, rotation, etc.)

1 Like

I tried it and it’s such a great module! Definitely going into my most useful modules folder.

1 Like

I’m guessing you did something like Instance:GetProperties() and saved that? Obviously this method does not exist but you could have created your own.

Thank you so. much.

Been needing something like this for a looooonggg time

1 Like

Yep, this is a serialization module with some customizable settings to help easily configure how the instance gets serialized or recreated.

This just uses Roblox’s Studio API to collect all instance properties. When Roblox adds new properties or instance types, the module will always be up to date if FetchLatestPropertiesOnline is set to true (requires HTTP enabled).

1 Like

Very nice! Just I got a dumb thing to ask, do I add the

local ConverterModule = require(9768577563) --Define and require the Module.
local InstanceToConvert = game.Workspace.Baseplate --Define an instance.
local ConvertedTable = ConverterModule:Convert(InstanceToConvert) --Create a variable for the converted data.
print(ConvertedTable) --Do stuff with the converted data.

As a local script? Or server? Or module? And do I put that script in the instance? I am confused on the “how to dos” of this

1 Like

You can call the module with whatever method works best for you. If you are using the module in its assetId form require(9768577563) you just need to make sure it’s being called with server context (a clientside local script will not work). This is a safety feature that Roblox has to prevent clients from easily executing remote modules.

If you are using the module in its instance form
require(game.ServerStorage.ConverterModule) the best practice is to keep the converter module in ServerStorage or ServerScriptService.

Regardless, calling it from the client will either throw a studio error or a proprietary-safeguard error.

As a general rule, I would personally recommend that you always have your scripts in ServerScriptService instead of instance based. This helps form good habits for (something similar to) single script architecture, which makes it easier to track down errors, change multiple dependencies that rely on the same code, and is much more flexible/expandable when you decide to make changes in the future.