Convert an object to its source code (With circular references and PROXIES too!)
Now even in the browser! Just add this to your HTML code...
<script src="https://cdn.jsdelivr.net/gh/AFatNiBBa/uneval@latest/main.js"></script>
...or this to your JavaScript
document.head.append(Object.assign(document.createElement("script"), { src: "https://cdn.jsdelivr.net/gh/AFatNiBBa/uneval@latest/main.js" }));
Always update to the latest version to have more features and bug fixes (A looot of bug fixes!)
npm r uneval.js & npm i uneval.js
If you get any error while installing or the Proxies are not stringified properly refer to the "warning" section of this
You can both import the package like this...
const uneval = require("uneval.js");
...and like this
const { uneval } = require("uneval.js");
Simply pass an object to the function as an argument to obtain the source code and eval it to obtain the object again.
You additionally can give some options to personalize the output
const a = {};
a.b = a.c = { a, url: /^(\+0?1\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}$/ig };
a.e = a;
console.log(uneval(a, { tab: " " }));
And the output will be
((x = {}) => (
x[1] = {
c: x[2] = {
url: /^(\+0?1\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}$/gi
},
b: x[2]
},
x[2].a = x[1],
x[1].e = x[1]
))()
Note that the complexity of the output depends on the complexity of the input
console.log(uneval([1, { a: "hi" }], { tab: 0, endl: 0 })) // [ 1, { a: "hi" } ]
You can save an object to file using the write function (Only in node)
uneval.write("./filename.js", [1, { a: "hi" }], { tab: " " });
Then "filename.js"
will contain
module.exports = [
1,
{
a: "hi"
}
];
Additionally to the object to stringify you can pass an option object to personalize your output. The option object will change during the process, it is advised that you don't use it elsewhere. The available options are:
namespace
- A
Map
that translates specific instances into their source - It defautls to
undefined
- A
pretty
- Setting it to
false
deactivatesspace
,endl
andtab
- It defaults to
true
- Setting it to
space
- Set the string that will replace the spaces in the output
- Setting it to
false
is like setting it to""
- It defaults to
" "
endl
- Set the string that will replace the new lines in the output
- Setting it to
false
is like setting it to""
- It defaults to
"\n"
tab
- Set the string that will replace the tabs in the output
- Setting it to a number "n" is like setting it to the
space
option repeated "n" times - Setting it to
false
is like setting it to""
- It defaults to
"\t"
custom
- If is set to
false
the custom conversions will be ignored - It defaults to
true
- If is set to
method
- If is set to
false
allows only the safe, but way uglier, syntax for objects methods - It defaults to
true
- If is set to
proxy
- If it is set to
true
stringifies proxies as they really are - It defaults to
true
(In the browser it is always counted asfalse
because it uses things that are only in node)
- If it is set to
proto
- Saves the class of objects (Using
Object.setPrototypeOf()
) - It defaults to
true
- Saves the class of objects (Using
depth
- If not
Infinity
specifies the maximum depth in which the object should be serialized - The object and its prototype are considered on the same depth
- At least
1
- It defaults to
Infinity
- If not
call
- If
false
wraps forcibly the result in a function but doesn't evaluate it - It defaults to
true
- If
safe
- Wraps object literals in brackets to not confuse them with blocks
- It's always
true
if the object is in the wrapper function - It defaults to
true
func
- Put the top object in a function that defines the cache variable
- It defaults to
true
val
- The name of the variable which will cache the repeated references
- It defaults to
"x"
conv
- Only in the "write" function
- If
false
the function will assume that the object is already serialized - It defaults to
true
export
- Only in the "write" function
- The code that will be put in front of the object source
- If it is an object then the value of the property
pre
will be concatenated before the object while the value ofpost
will be concatenated after - It defaults to
"module.exports = "
, the spaces will be the ones defined in the options
stats
- Not an option
- Its an object containing stats on the last execution (If you keep the settings object) and its necessary for the
uneval()
function
Note that in every option which accepts a boolean you can put 0
to represent false
and everything not "falsy" to represent true
.
-
All the things supported by json
-
Multiple references (Even in Symbol keys)
In boxed symbols both the object and the primitive version can be referenced
-
Circular references (Are much worse to implement, trust me)
-
Sparse arrays
-
Buffer
Only in node.js, not in Web.
-
Proxies
Only in node.js, not in Web.
To inspect proxies i used a node internal function, but i noticed it is no longer available in newer versions of node, so i created my own native module to handle that, node will build the module from source when you download it, but that is a process that uses externals tools in your machine that may be not available. The function will try to use the old method if the new one is not available.
-
The Global object
-
undefined
-
-0
,NaN
,Infinity
,-Infinity
-
Symbols
-
Symbol keys
-
Functions
-
Global native (and command line API) functions
-
Regular Expressions
-
Maps
-
Sets
-
Dates
-
Boxed Primitives, like
new String("hello")
-
New syntax for object's methods, like
{ func() {} }
(It'll look very bad with multiple references)Note that generating like that a method called "function" would work by default
-
Big Integers
-
Objects with a
null
prototype -
The
"__proto__"
key as a real property and not as a way of setting the prototype -
Custom types
-
Custom conversions
To define your own conversion you just need to define a function in your object (Or in its prototype) under the symbol
uneval.utils.customSource
(OrSymbol.for("uneval.utils.customSource")
when uneval is not in the current context)class NumArray extends Array { value = "test" } NumArray.prototype[Symbol.for("uneval.utils.customScan")] = function( out, // Object that will represent 'this' opts, // The options that were passed to uneval with defaults applied cache, // A map from object to structure (like "out") prev, // A function to comunicate with the previous call to "uneval.scan()" parent, // An object that define where 'this' is define on its parent uneval // The library, to not have clojures ) { return out; } // It doesn't scan anything because it only uneval numbers NumArray.prototype[Symbol.for("uneval.utils.customSource")] = function( struct, // Object that represents 'this' opts, // The options that were passed to uneval with defaults applied level, // A lot of "opts.tab" to put infront of every line of your code uneval // The library, to not have clojures ) { return `new NumArray(${ this.join("," + opts.space) })` } const a = new NumArray(1, 2, 3); a.b = { c: 4 }; console.log(uneval([ a, a.b ], { pretty: 0 }));
It's a bit advanced but you SHOULD define the scan too, it is what has to get the structure of the object, if you leave the default it will scan things that may be not included in the generated source, but they will still be referenced
Custom code:[new NumArray(1,2,3),{c:4}]
Custom without scan:
((x={})=>[Object.setPrototypeOf(new NumArray(1,2,3),(class NumArray extends Array { value = "test" }).prototype),x[1]])()
If you execute them you will notice that on the custom one that has not the scan defined there is a reference (
x[1]
) that is not defined anywhere, and it scanned the prototype ofNumArray
including it in the generated source, which is redundant. -
Custom definitions
Similiar to custom conversions, but it works on specific instances and with non objects too (View the
namespace
option for more details) -
The module itself 😳
- Clojures (Functions that access external local variables)
If you create a function with the new object method syntax and a computed key...
uneval({ [`val${ index }`]() { return 1; } });
...you have to be carefull with what is inside the computed field too. To avoid this problem simply set the
method
option tofalse
, this will use the (ugly) safe syntax, if the function is referenced elsewhere is used by default. If a value is defined inside a non-safe method it will not be saved, additionally if it is the first time you include it all other references will beundefined
- Native functions and Command Line API functions (Web) (If not global), and their eventual user defined custom properties
- Class static properties or prototype properties that are not defined in the class block
- Getters and Setters (For now are counted as normal properties)
- On an object of a special class, if you add a custom property with a key which is one of those that the special class uses by default, then that property may be skipped (Look at
[ ...uneval.utils.managedProtos.keys() ].map(x => x.constructor.name)
for a full list)const a = function b() {}; a.name = { a: 1 }; console.log(eval(uneval(a)).name); // b
- If a
Proxy
is inside of its target, is used a special kind of circular reference in which the value will be defined, there could be a reference to that value before it has been definedconst a = {}; a.b = a.c = new Proxy(a, {}); console.log(eval(uneval(a)).b) // undefinded