Get Every Property Of An Instance

In this tutorial, we will explore how to create a Luau module script that retrieves and lists properties for a specific class. Whether you’re working on a game, plugin, or any Luau-based project where classes play a crucial role, knowing how to extract and work with class properties is essential. I know there are different modules that provide this but I just wanted to write a tutorial explaining on how it is done.

First thing, it cannot be done without HTTP Service. So go to the Game Settings → Security → Allow HTTP Requests and turn it on

The way this is going to work is using HTTP Service to return a JSON table, decoding it and then sorting it for different classes allowing you have to have access to the properties. It is really simple

Let’s Begin with a simple module

We will define some variables such as

  • HTTP Service → local HTTPService = game:GetService("HttpService")
  • The api url → local URL = "https://anaminus.github.io/rbx/json/api/latest.json"

That URL leads to a page with a JSON table which contains every Enum, Property and Class of an instance.

We can also add a Classes table since we will store every class in it. Since we will be using :GetAsync and it yields, there isn’t a point of calling :GetAsync for every class, we will just use it once, it will sort the data and fill the Classes table with every class and its properties and then retrieve it using the table.
Thus will yield only once instead of as many times as we want get the properties of an instance. I hope that made sense.

Then we will have a different function that will process and sort that data. Let’s call it ProcessAPIData

Now, Let’s make a simple module, that will return a function which will return a string array with the names of the class’ properties we want, with the things from above

local HTTPService = game:GetService("HttpService")
local URL = "https://anaminus.github.io/rbx/json/api/latest.json"

local Classes = {}

function GetAPIData()
      
end

function ProcessAPIData()
     
end

return function(ClassName: string) : {string}
      return Classes[ClassName]
end

Let’s start with the GetAPIData function.

Incase something goes wrong, we will put GetAsync in a pcall() (protective call). So if there are any errors, the script won’t stop running.

Inside of the pcall() we will put an anonymous function that will just return the HTTPService:GetAsync with the URL

function GetAPIData()
	local success, response = pcall(function()
		return HttpService:GetAsync(URL) -- that will put the data, returned from the URL, inside of the response variable
	end)

      --[[now we will check if it was successful of not.
          if not, then we will wait 3 seconds, for example, and return the GetAPIData. 
          That will create a loop until it is successful. 
          If it is successful we will just return the response.
     -- ]]

	if success then
		return response
	else
		warn(response) -- warning the error (for debugging purposes)

		task.wait(3)

		return GetAPIData()
	end
end

and our GetAPIData() function is ready.

Let’s move to the ProcessAPIData function.

Let’s first get that API data using the GetAPIData function

local apiData = GetAPIData()

This data will be a JSON formated table, we will have to decode it using the HTTPService:JSONDecode() function.

apiData = HTTPService:JSONDecode(apiData)

or your code so far should look something like this

local HttpService = game:GetService("HttpService")
local Classes = {}

local URL = "https://anaminus.github.io/rbx/json/api/latest.json"

function GetAPIData()
	local success, response = pcall(function()
		return HttpService:GetAsync(URL) -- that will put the data, returned from the URL, inside of the response variable
	end)

      --[[now we will check if it was successful of not.
          if not, then we will wait 3 seconds, for example, and return the GetAPIData. 
          That will create a loop until it is successful. 
          If it is successful we will just return the response.
     -- ]]

	if success then
		return response
	else
		warn(response) -- warning the error (for debugging purposes)

		task.wait(3)

		return GetAPIData()
	end
end

function ProcessAPIData()
      local apiData = GetAPIData()
      apiData = HTTPService:JSONDecode(apiData)
end

return function(ClassName: string) : {string}
     return Classes[ClassName]
end

If we print the apiData from the ProcessAPIData function, it will print a really long array, over 4000 key-value pairs.

The values from this array are tables. They can be 3 variants - for Classes, Properties and Enums.
For Classes should look something like this:

["Name"] = "VirtualInputManager",
["Superclass"] = "Instance",
["tags"] = {},
["type"] = "Class"

For Properties, something like this:

["Class"] = "Vector3Value",
["Name"] = "Value",
["ValueType"] = "Vector3",
["tags"] = {},
["type"] = "Property"

And we don’t care about Enums.

Now, we know how the api data looks. We can start “sorting” it.

First we will iterate/loop though the array
and let’s call the value an “entry”.

Since it is an array, we will use ipairs (I am not 100% sure about it, but I think it is a little bit faster than pairs when we are talking about arrays)

for _, entry in ipairs(apiData) do
      
end

now lets check the type, to see if it is a class or a property. We will use the type key from the “entry” table. So you can simply do

local entryType = entry.type

now let’s check if the entryType is a Class or a Property.

if entryType == "Class" then

elseif entryType == "Property" then

end

We are going to do first the Class.

this is how the “entry” should look (if you don’t remember)

["Name"] = "BasePart", -- the class name
["Superclass"] = "PVInstance", -- the super class
["tags"] = {}, -- tags
["type"] = "Class" -- the type

Let’s first explain what a Superclass is. A Superclass is basically a parent/base class which subclasses inherite its behavior.

In this case our class - BasePart is like a subclass of PVInstance.
And the PVInstance is the Superclass

I hope you understood.

As I said earlier, we will continue with the Class entryType.

let’s just save those values (from the entry) into variables

local className = entry.Name
local Superclass = entry.Superclass

let’s do a check if the Superclass exists.

if Superclass then
     
end

Now here gets a little confusing.
If it isn’t nil, we probably have it saved in the Classes table.

so we can do

if Superclass then
     local superclassData = Classes[Superclass]
end

if so then we can just iterate/loop though that data and assign the new class with the values since they will be exactly the same, since the class’ behavior is inherited from the Superclass’ one

if Superclass then
     local superclassData = Classes[Superclass]

     if superclassData then
           for _, data in ipairs(superclassData) do

           end
     end
end

But how will we store that data?

Let’s move up a little bit and make a table right next to the className and Superclass variables.

local className = entry.Name
local Superclass = entry.Superclass
local classData = {}

now when we are iterating over the array, we can insert the values inside that classData table

for _, data in ipairs(superclassData) do
     table.insert(classData, data)
end

and then we just insert that classData into the Classes table using className as the key

Classes[className] = classData

and that’s it for the entryType Class

your function should be looking something like this so far

local function ProcessApiData()
	local apiData = GetAPIData()
	apiData = HttpService:JSONDecode(apiData)
	
	for _, entry in ipairs(apiData) do
		local entryType = entry.type

		if entryType == "Class" then

			local className = entry.Name
			local classData = {}
			local Superclass = entry.Superclass

			if Superclass then
				local superclassData = Classes[Superclass]

				if superclassData then
					for _, data in ipairs(superclassData) do
						table.insert(classData, data)
					end
				end
			end

			Classes[className] = classData
		elseif entryType == "Property" then

		end
	end
end

Let’s move onto the entryType Property
It is way too simple.

We know how the Property “entry” looks like (if somebody forgot)

["Class"] = "BasePart",
["Name"] = "BrickColor",
["ValueType"] = "BrickColor",
["tags"] = {},
["type"] = "Property"

We have the Class and the property name. We do not need anything else.

Let’s assign variables for some of them

local className = entry.Class
local propertyName = entry.Name

We have Class, the Property Name. So we can directly access the class by doing
Though first, I would personally want to check if there are no tags associated, so I will just do.

if not next(entry.tags) then

end

That will skip properties with tags.

And now we can just do inside of the check

local classData = Classes[className]

to access the classData table which we will insert the propertyName inside.
Let’s just check if the classData isn’t nil and then we will insert it using table.insert

if classData then
      table.insert(classData, propertyName)
end

and boom, your ProcessAPIData function is done. It should look something like this:

local function ProcessApiData()
	local apiData = GetAPIData()
	apiData = HttpService:JSONDecode(apiData)
	
	for _, entry in ipairs(apiData) do
		local entryType = entry.type

		if entryType == "Class" then

			local className = entry.Name
			local classData = {}
			local Superclass = entry.Superclass

			if Superclass then
				local superclassData = Classes[Superclass]

				if superclassData then
					for _, data in ipairs(superclassData) do
						table.insert(classData, data)
					end
				end
			end

			Classes[className] = classData
		elseif entryType == "Property" then
			local className = entry.Class
			local propertyName = entry.Name

			if next(entry.tags) then return end
			
			local classData = Classes[className]

			if classData then
				table.insert(classData, propertyName)
			end
		end
	end
end

now let’s call it at the end of the module. I personally would just let it yield instead of spawning it on another thread but that is just my preference.

And yeah, the module is done, really simple but effective. Here is how it should look

local HttpService = game:GetService("HttpService")
local URL = "https://anaminus.github.io/rbx/json/api/latest.json"

local Classes = {}

function GetAPIData()
	local success, response = pcall(function()
		return HttpService:GetAsync(URL) 
	end)

	if success then
		return response
	else
		warn(response)

		task.wait(3)

		return GetAPIData()
	end
end

local function ProcessApiData()
	local apiData = GetAPIData()
	
	apiData = HttpService:JSONDecode(apiData)
	
	for _, entry in ipairs(apiData) do
		local entryType = entry.type

		if entryType == "Class" then
			
			local className = entry.Name
			local Superclass = entry.Superclass
			local classData = {}
			
			if Superclass then
				local superclassData = Classes[Superclass]

				if superclassData then
					for _, data in ipairs(superclassData) do
						table.insert(classData, data)
					end
				end
			end

			Classes[className] = classData
			
		elseif entryType == "Property" then
			
			local className = entry.Class
			local propertyName = entry.Name

			if not next(entry.tags) then
				local classData = Classes[className]

				if classData then
					table.insert(classData, propertyName)
				end
			end
		end
	end
end

ProcessApiData()

return function(ClassName: string)
	return Classes[ClassName]
end

Now you may be wondering how to use it. It is fairly simple.

Just require the module

local module = require(path.to.module)

and just call it with the ClassName you want

module("BasePart")

and let’s print it

print(module("BasePart"))

This is what the output looks like:

{
	[1] = "Archivable",
	[2] = "Name",
	[3] = "Parent",
	[4] = "Anchored",
	[5] = "BackParamA",
	[6] = "BackParamB",
	[7] = "BackSurface",
	[8] = "BackSurfaceInput",
	[9] = "BottomParamA",
	[10] = "BottomParamB",
	[11] = "BottomSurface",
	[12] = "BottomSurfaceInput",
	[13] = "BrickColor",
	[14] = "CFrame",
	[15] = "CanCollide",
	[16] = "CollisionGroupId",
	[17] = "Color",
	[18] = "CustomPhysicalProperties",
	[19] = "FrontParamA",
	[20] = "FrontParamB",
	[21] = "FrontSurface",
	[22] = "FrontSurfaceInput",
	[23] = "LeftParamA",
	[24] = "LeftParamB",
	[25] = "LeftSurface",
	[26] = "LeftSurfaceInput",
	[27] = "Locked",
	[28] = "Material",
	[29] = "Orientation",
	[30] = "Position",
	[31] = "Reflectance",
	[32] = "RightParamA",
	[33] = "RightParamB",
	[34] = "RightSurface",
	[35] = "RightSurfaceInput",
	[36] = "RotVelocity",
	[37] = "Rotation",
	[38] = "Size",
	[39] = "TopParamA",
	[40] = "TopParamB",
	[41] = "TopSurface",
	[42] = "TopSurfaceInput",
	[43] = "Transparency",
	[44] = "Velocity"
}

This is the simplest way I could think of doing the module.

Hope you make a great use of it!

5 Likes

A solution that doesn’t use HTTP Service

2 Likes