Stringify - Module to convert (almost) any object into valid lua

EJFace

I made a function that will print any object as valid lua for an admin I'm working on that has a plugin to automatically update its config with the latest version of the config ( adding in missing values, removing no longer used values, etc ).

Let me know if there are any issues/features you’d like, or dont, I’m not your boss! You’re free to use this with or without credit, I honestly don’t care, so go mad!

NOTES:

  1. Currently instances are just converted to a path ( e.g. workspace:WaitForChild( “Base” ) ). I would like to support creation of instances too however not sure how I’d differentiate between what you want to occur. I’m thinking maybe an option called Create which is a function that when returns true for an object the object is created instead of located.
  2. Cyclic tables only work if the table is being ToString’d with a name so the function can do name.Cycle = name
  3. Userdata, threads and functions won’t work for obvious reasons
  4. Works with any object, not just tables, e.g. ToString( “Hi” )

Here’s a link to the model on Roblox for you to take/require ( returns a function ):

And here’s a link to the module on github:

Options it allows ( Examples of uses below) ( Several taken from Inspect ):

Space = " " -- Uses this character anywhere my script adds a space
Tab = "	" -- Uses this character anywhere my script adds a tab
NewLine = "\n" -- Uses this character anywhere my script adds a new line that is required for it to be valid lua ( Can be replaced with spaces or semi-colons and still be valid lua )
SecondaryNewLine = "\n" -- Uses this character anywhere my script adds a new line that is just for visuals sake
MaxDepth = math.huge -- Maximum depth in a table this will go
MaxDepthReplacement = nil -- Defaults to { ... }, this string will be used for a table that bypasses the max depth
Process = function ( Obj, Name, Options, Tabs, Cyclic, Key, CyclicObjs, WaitedFor, NumKey ) -- A function called every time an object is processed ( Name will be formatted with tabs / spaces already )

A few tests of things it can handle:
1.

>local Instance_Test = print( Stringify( { game.Lighting.Candy, workspace.Base, workspace.Base.Base } ) )

{ 
	
	game:GetService( "Lighting" ):WaitForChild( "Candy" ), 
	
	workspace:WaitForChild( "Base" ), 
	
	workspace.Base:WaitForChild( "Base" )
	
 }
>local WaitForChild_Test = print( Stringify( { game.Lighting.Candy, workspace.Base.Base.Base, workspace.Base, workspace.Base.Base } ) )

{ 
	
	game:GetService( "Lighting" ):WaitForChild( "Candy" ), 
	
	workspace:WaitForChild( "Base" ):WaitForChild( "Base" ):WaitForChild( "Base" ), 
	
	workspace.Base, 
	
	workspace.Base.Base
	
 }
>local Depth_Test = print( Stringify( { 1, { 2, { 3, { 4, { 5, { 6, } } } } } }, "depth_test", { MaxDepth = 3, Space = " ", Tab = "", NewLine = " ", SecondaryNewLine = "" } ) )

depth_test = { 1, { 2, { 3, { ... } } } }
>local Depth_Test_2 = print( Stringify( { 1, { 2, { 3, { 4, { 5, { 6, } } } } } }, "depth_test", { MaxDepth = 3, MaxDepthReplacement = "", Space = " ", Tab = "", NewLine = " ", SecondaryNewLine = "" } ) )

depth_test = { 1, { 2, { 3,  } } }
>local String_Test = print( Stringify( { "Hi!\" lol", '"Hi there!" :P', "String with new\n line!", [[String with non-processed new line \n]], "String with \\special %! \\n \\\" ' [[ characters", "New line with [[\n]]" }, "Hm that's odd" ) )

getfenv( )[ "Hm that's odd" ] = { 
	
	'Hi!" lol', 
	
	'"Hi there!" :P', 
	
	[[String with new
 line!]], 
	
	"String with non-processed new line \\n", 
	
	"String with \\special %! \\n \\\" ' [[ characters", 
	
	[[New line with \[\[
\]\]]]
	
 }
>local Number_Test = print( Stringify( { 1, 2, math.huge }, "Uh.what" ) )

Uh.what = { 
	
	1, 
	
	2, 
	
	tonumber( "1.#INF" )
	
 }
>local Process_Test = print( Stringify( { workspace.Base, true }, nil, { Process = function ( Obj, Name, Options, Tabs, Cyclic, Key, CyclicObjs, WaitedFor, NumKeyr ) if typeof( Obj ) == "Instance" then return Name .. 'Instance.new( "Part" )' end end } ) )

{ 
	
	Instance.new( "Part" ), 
	
	true
	
 }
>local t = { a = { true, [ "1a" ] = { CFrame.new( 1, 0, 1 ), [ "t.1" ] = true }, [ true ] = { CFrame.new( 1, 0, 1 ) } }, [ "1" ] = 2, _a1 = true, { }, { }, 2, 3 } t.t = t.a[ "1a" ] t[ "1t" ] = t
>local Cyclic_Table_Test = print( Stringify( t, "t" ) )

t = { 
	
	{ }, 
	
	{ }, 
	
	2, 
	
	3, 
	
	[ "1" ] = 2, 
	
	_a1 = true, 
	
	t = { 
		
		CFrame.new( 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1 ), 
		
		[ "t.1" ] = true
		
	 }, 
	
	a = { 
		
		true, 
		
		[ true ] = { 
			
			CFrame.new( 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1 )
			
		 }
		
	 }
	
 }

t.a[ "1a" ] = t.t

t[ "1t" ] = t
>local t = { a = { true, [ "1a" ] = { CFrame.new( 1, 0, 1 ), [ "t.1" ] = true }, [ true ] = { CFrame.new( 1, 0, 1 ) } }, [ "1" ] = 2, _a1 = true, { }, { }, 2, 3 } t.t = t.a[ "1a" ] t[ "1t" ] = t
>local Space_Tab_NewLine_SecondaryNewLine_Test = print( Stringify( t, "t", { Space = "", Tab = "", NewLine = " ", SecondaryNewLine = "" } ) )

t={{},{},2,3,["1"]=2,_a1=true,t={CFrame.new(1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1),["t.1"]=true},a={true,[true]={CFrame.new(1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1)}}} t["1t"]=t t.a["1a"]=t.t
>local t = setmetatable( { a = 1, b = 2 }, { __index = { c = 3 }, __newindex = function ( ... ) rawset( ... ) end } )
>local metatable_test = print( Stringify( t, "metatable_test" ) )

metatable_test = setmetatable( { 
	
	a = 1, 
	
	b = 2
	
}, { 
	
	__index = { 
		
		c = 3
		
	}, 
	
	__newindex = function ( ) error( "Can't run ToString functions" ) end
	
}
10 Likes

Hm - just to bring up something, 0/0 is converted to tostring("-1.#IND"), which returns nil.
Seems pretty cool though - What was your incentive for making this?

As for table parsing, you don’t check numeric keys (i.e. in arrays).

local t = {a = 2, b = {}}
t.b.a = t
print(ToString({“hello”, 0/0, t}, “test”))

The table t was shown as test.3, which isn’t valid :confused:


Functions are simply tostring’ed - { function: 28C194D8 } isn’t valid Lua syntax.
EnumItems are wrapped as EnumItem.new( Enum.NormalId.Top ), but EnumItem isn’t defined in default environments.
Things like BrickColors don’t have their input properly wrapped - BrickColor.new( Cool yellow ) will error.

2 Likes

Can’t do functions for obvious reasons. I’ll fix the enums, brickcolors, numeric keys ( thought I had done this ) and 0/0 now.

Make this the stringified version of all functions?

function() error("stringified functions cannot be called", 2) end

:wink:

1 Like

Okay :slight_smile:

@As8D @buildthomas
Fixed those in the OP:

>local t = { Enum.AccessType.Me, BrickColor.Red( ), 0/0, math.huge }
>t.t = t
>print( ToString( { t, function ( ) print"hi" end }, "depth_test" ) )
depth_test = { 
	
	{ 
		
		Enum.AccessType.Me, 
		
		BrickColor.new( "Bright red" ), 
		
		0 / 0, 
		
		math.huge, 
		
	 }, 
	
	function ( ) error( "Can't run ToString functions" ) end
	
 }

depth_test[ 1 ].t = depth_test[ 1 ]
3 Likes

OP now works with metatables :slight_smile:

Not sure if anyone is using this but I’ve updated the OP with the following:

Fixed numbers being converted to strings if they contained decimals
Fixed long strings not being converted to valid lua under certain circumstances
Fixed Color3s being rounded incorrectly causing certain BrickColor.X.Colors to be wrong

Added support for NumberRange, ColorSequence, and NumberSequence

Curious… why do you double-space your code and add seemingly unnecessary spaces around parenthesis and brackets?

I personally find it nicer to look at and easier to read however I know a lot of people would disagree with me.

Sorry to necro bump this, but if anyone was using this code I’ve now put it into a modulescript that includes several more fixes and improvements and the code is up on github

1 Like