uncycle v0.5.5
unCycle - handler to serialize and clone objects with circular references and RegExp
introduction
unCycleis a node.js module. That means it could be installed as one of dependency modules for some another node.js pacakge. 'uncycle' module has it's own dependent package namedregstr.
unCyclemodule provides a handler makingJSONcapable
to stringify and parse objects- with circular references and
- having
RegExpobjects as property values.
by product that means the capability to clone such objects and arrays
While having been installed locally the handler is loaded by :
h = require('./uncycle').handler;variant of use 1:
o = h.preStringify(o); // or simply h.preStringify(o);
oj = JSON.stringify(o);
ojo = JSON.parse(oj);
ojo = h.postParse(ojo); // or simply h.postParse(ojo);that's it.
variant 2:
oj = JSON.stringify( o, h.replacer.bind(h) );
ojo = JSON.parse ( oj, h.reviver.bind(h) );Remark 1:
Processing of original object o makes some changes in it.
If stringifying is not your final goal and you need o for further use
we should remove fingerprints and leave everything asItWas ( circularize
object again).
Method: h.circularize(o) is presumed for that purpose, i.e. :
v.1
h.preStringify(o);
oj = JSON.stringify(o);
h.circularize(o);
ojo = JSON.parse(oj);
h.postParse(ojo);v.2
oj = JSON.stringify(o, h.replacer.bind(h));
h.circularize(o); // if any
ojo = JSON.parse(oj, h.reviver.bind(h));Remark 2:
Variant 2 paraphrases standard use of JSON.stringify and
JSON.parse with two parameters, second of wich is a function permitting
modify output in accordance with JSON manual.
In our case such modification is related only with properties
being circular references. Nevertheless the initial standard ability
to modify handling object properties on each (key,value) bases of is
preserved as well. User defined replacer and reviver functions
in the context of JSON.stringify and JSON.parse documentation
should be assigned to handler methods:
h.replacerUser = replacer;
h.reviverUser = reviver; where replacer and reviver are functions of two parameters
reviver = function(key,value){...};
replacer = function(key,value){...};determined by user.
Cloning
Why not:
clone = h.postParse( JSON.parse( JSON.stringify( h.preStringify(o))));
// or
clone1 = JSON.parse( JSON.stringify( o, h.replacer.bind(h)), h.reviver.bind(h));In a Browser
Get the sample of library to use in a browser from here https://github.com/vuGitH/UncycleInBrowser
tests
To varify this write yours or get test object and user functions samples for test object as follows:
var o = h.getTestObj();where
h.getTestObj=function(){
var o = {
a:{},
b:[0, {id: 'inarr', ob: {}},2],
c:{o: {},o1: [0,[],2], o3: 'o3',im: 'obj'}
};
o.a = o;
o.b[1].ob = o.b;
o.c.o1[1].push(1,o.c);
o.d = o.a;
o.f = 'f';
o.re = /standrdRegExp$/ig;
o.prim = {a: 'a',ar: [12,13]};
o.aa = {p1: 'p1', p2:2};
return o;
};
var replacer = function(key,value){
return key==='f'?undefined:value;
};
var reviver = require('./uncycle').reviver;
h.replacerUser = replacer;
h.reviverUser = reviver;and analyse
oj = JSON.stringify(o, h.replacer.bind(h));
h.circularize(o);
ojo = JSON.parse(oj, h.reviver.bind(h)); Differences between these two objects are appropriate to modifications
determined by user functions <replacer> and <reviver>.
Peculiar Feature!:
Suppose we have ordinary object witout circular references.
The Handler eats it without changing of codes, e.g.
var oIn = {a: 'a', ob:{a: 'a', b: []}, arr: [1,2,3]};So, consecutive stringify and parse in one line gives:
var oOut = JSON.parse(JSON.stringify( outCirc, h.replacer.bind(h)), h.reviver.bind(h));
// check that oOut is
{a :'a', ob: {a:'a', b: []}, arr: [1,2,3]} object but
oIn === oOut ? true : false ; // returns false - another objectthe same result with circularize step:
oj = JSON.stringify( oIn, h.replacer.bind(h));
h.circularize( oIn );
oOut = JSON.pars( oj, h.reviver.bind(h) );RegExp JSON - stringify - parse option
As you could already have noted the property re of testing object o
is regular expression object and instead of this handler has worked
it out successfully.
Dependent package regStr is used for that purpose.
Boolean property h.regStrOn is dedicated to switch
on/off dependent package regStr using to stringify and parse RegExp
properties of objects. The Default value of h.regStrOn===true;
To switch off set it to false, typical JSON behaviour regarding RegExp will take place after that.
regStr module(package) has analogus logic and structure like uncycle.
For Details and algorithm of both packages see descriptions, comments
and codes. type npm run explain or require('./explain_uncycle')
Best regards! Vladimir
uncycle module installation remarks
This instruction clarifies steps and some aspects of how to download, to install and begin to use the package
uncycle.For further consideration let's consider that our goal is to install
uncyclefor usage in your project who's root directory (your package location directory) issomeDisk:\some-path-to\your-proj-dir. I will denote it as~/your-proj-dir/`Some versions of node.js framework and npm packages manager are presumed to be installed on your pc.
Explanaions are given presuming Windows Command Prompt environement.
source
The package Source repository front page
steps
- Open cmd Command Prompt Shell
Ctl+R cmd {Enter}
- Go to the root directory of your package.Make it as the current one. This may be just new empty directory named as has been mentioned earlier. Download and install the module locally using npm package manager
cd /d ~\your-proj-dir
npm i uncycleAfter the completion you'll see
added 2 packages in 3s
~\your-proj-dir>and you could note something new in your dir:
- the subdirectory
node_moduleshas been created in~\your-proj-dir\dir with two subfoldersuncycleandregstr:
~\your-proj-dir
|-- node_modules
|-- uncycle
|-- regstr- To check the correctness of downloading an installation go to the appropriates subpackage directories and run test commands:
at first to theuncycledir
cd .\node_modules\uncycle\
npm run testSee the test output layout and then
go to the regstr dir
cd ..\regstr\
npm run test
:: see test results- Return to you package root folder and Let's check if both packages are reachable
cd ..\..\now we again are inside ~\your-proj-dir\ directory. Type
node
require.resolve('uncycle')the output will be the full path to the uncycle.js file including diskLetter:\\etc\\..\\your-proj-dir\\node_modules\\uncycle\\uncycle.js
Repeat the same command for regstr
> require.resolve('regstr')Look for the output being something like this:
diskLetter:\\etc\\..\\your-proj-dir\\node_modules\\regstr\\regstr.js
If you would have seen everything as is described then in any place of your .js files inside your project directory you could have access to uncycle using the command
// -- for uncycle --
/* @type {Object} */
var uh = require('uncycle').handler;
// or alternatively
var uh1 = require('./node_modules\uncycle\uncycle.js');
// -- for regStr ---
/* @type {Object} */
var r = require('regstr').regStr; // in .regStr S is capital
// or alternatively
var uu = require('./node_modules\regstr\regstr.js').regStr;Of course let or const could be used for assignement.
In a browser
Here is a sample of codes to use of to make some test in a browser environment:
https://github.com/vuGitH/UncycleInBrowser.git
Explanation and details
Each package has it's own explanation of usage, algorithm's details and is commented in details in script files. To get explanation in command propmt go into appropiate root folder of the package and run cmd commands
for uncycle
npm run explainornpm run readme- or both
- or read explain.txt file in some text editor
- to test type
npm run test
for regstr
npm run explainnpm run explain-ciphnpm run explain-deciph- to test type
npm run test
Algorithm and examples
---- unCycle module ---- .
unCycle is a handler who supplies methods for:
1. ciphering and deciphering objects and arrays with circular references and RegExp
properties enabling JSON object to stringify and parse them back
by means of JSON.stringify() and JSON.parse() methods and/or
- provides
replacerandreviverfunctions (second parameter ofJSON.stringify()andJSON.parse()method) to stringify and parse objects and arrays with circular references andRegExpproperties.
Let's take some test object o
var o = {
a: {},
b: [0, {id: 'inarr', ob: {}}, 2],
c: {o: {}, o1: [0,[],2], o3: 'o3', im: 'obj'}
};and add few internal circular referrences into it:
o.a = o;
o.b[1].ob = o.b;
o.c.o1[1].push(1,o.c);So the object is now presented in node console:
{ a: [Circular],
b: [ 0, { id: 'inarr', ob: [Circular] }, 2 ],
c: { o: {}, o1: [ 0, [Array], 2 ], o3: 'o3', im: 'obj' } }Pay attention to 'Circular' mark indicating presence of circular referrences
unCycle handler uses a concept of "universal identifiers" - uids -
which presumes that any hierarchycal structure - a set of objects,
sub-objects, sub-arrays, their sub-objects, and sub-arrays, as well
as their primitive properties and in general sense methods could be
represented as properties of some top universal object oU
and appropriate uid - universal identifyer -
anyProp = oU[uidOfAnyProp]where uidOfAnyProp = uidOfParentOfAnyProp + anyPropId
Concrete object is hierarchical structure as well.
Assigning uid to each property of object o presumes that
if some property has it's own id (for ex. property's name or a key),
it's uid = pUid + id,
where pUid is uid of a parent object or array, an appropriate another
property of object o who itself is a parent object of the property in
consideration.
Now if some Entity (result of original object conformation) is object oU
value of any property of original object o could be obtained
by equation propValue = oU[uid], the same on the local level = o[id]
It is important to note that if o[id] is so called local reference,
implying that o is parent of child id but itself is a child of own
preparent,oUuid` could reach any sub-level of object's hierarchy.
Similarly parent object pO = oU[pUid].
For any analising object unCycle creates uidsDirectory (shortly uiDirect)
the Structure or that Entity object, the image or conformation
of original o object containing accumulative arrays and individual
properties, presenting properties' and subproperties' uids and
values:
uiDirect for object o shown above looks like this (stirngs
beginning with "#" and containing "#"-signs inside are, as one would
have guessed, that <uids>):
{ vals:
[ { a: '#', b: [Array], c: [Object], uid: '#' },
[ 0, [Object], 2 ],
{ id: 'inarr', ob: '##b', uid: '##b#1' },
{ o: [Object], o1: [Array], o3: 'o3', im: 'obj', uid: '##c' },
{ uid: '##c#o' },
[ 0, [Array], 2 ],
[ 1, '##c' ] ],
uids: [ '#', '##b', '##b#1', '##c', '##c#o', '##c#o1', '##c#o1#1' ],
oids: [ '', 'b', 1, 'c', 'o', 'o1', 1 ],
showUids: false,
resetData: [Function: resetData],
'#':
{ a: '#',
b: [ 0, [Object], 2 ],
c: { o: [Object], o1: [Array], o3: 'o3', im: 'obj', uid: '##c' },
uid: '#' },
'##b': [ 0, { id: 'inarr', ob: '##b', uid: '##b#1' }, 2 ],
'##b#1': { id: 'inarr', ob: '##b', uid: '##b#1' },
'##c':
{ o: { uid: '##c#o' },
o1: [ 0, [Array], 2 ],
o3: 'o3',
im: 'obj',
uid: '##c' },
'##c#o': { uid: '##c#o' },
'##c#o1': [ 0, [ 1, '##c' ], 2 ],
'##c#o1#1': [ 1, '##c' ] }
uid : '#'
value :
{ a: '#',
b: [ 0, { id: 'inarr', ob: '##b', uid: '##b#1' }, 2 ],
c:
{ o: { uid: '##c#o' },
o1: [ 0, [Array], 2 ],
o3: 'o3',
im: 'obj',
uid: '##c' },
uid: '#' }
uid : '##b'
value :
[ 0, { id: 'inarr', ob: '##b', uid: '##b#1' }, 2 ]
uid : '##b#1'
value :
{ id: 'inarr', ob: '##b', uid: '##b#1' }
uid : '##c'
value :
{ o: { uid: '##c#o' },
o1: [ 0, [ 1, '##c' ], 2 ],
o3: 'o3',
im: 'obj',
uid: '##c' }
uid : '##c#o'
value :
{ uid: '##c#o' }
uid : '##c#o1'
value :
[ 0, [ 1, '##c' ], 2 ]
uid : '##c#o1#1'
value :
[ 1, '##c' ]The handler has transformed original object into the form when values
of properties being circular references are exchanged by string values of
their uid-s.
Actually after such transformation the object
o =
{ a: '#',
b: [ 0, { id: 'inarr', ob: '##b', uid: '##b#1' }, 2 ],
c:
{ o: { uid: '##c#o' },
o1: [ 0, [Array], 2 ],
o3: 'o3',
im: 'obj',
uid: '##c' },
uid: '#' }As you can see there are no any Circulars in it.
Nevertheless when it is necessary handler could circularize it back
using method unCycle.circularize(o);
Look, this is our original object after back "circularization"
o =
{ a: [Circular],
b: [ 0, { id: 'inarr', ob: [Circular] }, 2 ],
c: { o: {}, o1: [ 0, [Array], 2 ], o3: 'o3', im: 'obj' } }All Circular at their original sites.
Before continuing, let's dwell on the uid details and the formalism utilized.
in common for any object( or array) in hierarchical structure
uid = pUid +'#'+ id;
pO = oU[pUid];
o = pO[id] and o = oU[uid]
o - child member(object or array) of parent object pO
uid - unversal identifier of o child
id - child identifier
pUid - universal identifier of parent object
oU - is the top root object of whole structure( uidsDirectory or uiDirect)We can get any structure's element by means of o=oU[uid]unCycle instanciates such oU naming it uidsDirectory or uiDirect
Idea of uid format and parts:
- each sub-level of structure is symbolized by "#"sign
so uid = parentUid + "#" + id;
- id of some property is alfanumerical
- id of array's element is it's index - digitalthe following draw explains uid-s format
uid - '##c#z#4' {string}
|
uid parts: # .. #c .. #z ..#4
uils= [ '' , 'c' , 'z' , 4 ] {string[]}
var oo={} | | | |
evalStr = 'oo ["c"] ["z"] [4]' ='oo["c"]["z"][4]';
eval(evalStr) -> eval( 'var oo={c:{z:["","","","",oo["c"]["z"][4]]}};')
(eval never used inside unCycle it's mentioned imaginaryly)unCycle.refer() methods returns variable reference using uid value
uiDirect object preserves original object properties values permitting
to manipulate with object during convertions and reconversions
o -> ojo transformation:
o -> preStringify(o) -> o // new state of o (the same object o===newStateOfO)
-> oj = json.stringify(o) ->
-> ojo = json.parse(oj)
-> ojo = postParse(ojo) // final "circularized" object
// equivalent to initial one ojo conforms o i.e. has identical properties and internal circular
references
As we have seen above the handler method
unCycle.preStringify(o);transformes original object into the form where values
of properties being circular references are exchanged by string values of
their uid-s, the form easyly serializable by JSON.stringify().
Another method -
unCycle.postParse(ojo);using as input parameter object ojo returned by
JSON.parse(oj), where oj is json string got from converted object o by
oj=JSON.stringify(o);transforms ojo object into the original "circularized" form.
Now let's consider another case of another test object
o=
{ a: [Circular],
b: [ 0, { id: 'inarr', ob: [Circular] }, 2 ],
c: { o: {}, o1: [ 0, [Array], 2 ], o3: 'o3', im: 'obj' } }The property unCycle.replacer provides replacer function which
could be used as second parameter of JSON.stringify(o,replacer) method
to stringify the circular object directly by means of JSON.stringify()
var oj = JSON.stringify(o, unCycle.replacer.bind(unCycle));and returns json string
oj ='
{
"a": "#",
"b": [
0,
{
"id": "inarr",
"ob": "##b"
},
2
],
"c": {
"o": {},
"o1": [
0,
[
1,
"##c"
],
2
],
"o3": "o3",
"im": "obj"
}
}
'For some future reasons we could leave object o in "circularized"
form, using code: unCycle.circularize(o) =
{ a: [Circular],
b: [ 0, { id: 'inarr', ob: [Circular] }, 2 ],
c: { o: {}, o1: [ 0, [Array], 2 ], o3: 'o3', im: 'obj' } }With mediation of unCycle handler json string oj
obtained after serialisation of object with circular references
could be parsed into object equivalent to original one
similarly in two optional ways:
- using method
unCycle.postParse(o1)aftero1 = JSON.parse(oj)or - using reviver function provided by method
unCycle.reviverpermiting parseojdirectly usingJSON.parsewith second parameter reviver
var o1 = JSON.parse(oj,reviver);where riviver function is obtained from the property unCycle.reviver:
reviver = unCycle.reviver.bind(unCycle);So, after parsing we get new object o1=
{ a: [Circular],
b: [ 0, { id: 'inarr', ob: [Circular] }, 2 ],
c: { o: {}, o1: [ 0, [Array], 2 ], o3: 'o3', im: 'obj' } }Or each property separately:
"a" :
{ a: [Circular],
b: [ 0, { id: 'inarr', ob: [Circular] }, 2 ],
c: { o: {}, o1: [ 0, [Array], 2 ], o3: 'o3', im: 'obj' } }
"b" :
[ 0, { id: 'inarr', ob: [Circular] }, 2 ]
"c" :
{ o: {}, o1: [ 0, [ 1, [Circular] ], 2 ], o3: 'o3', im: 'obj' }fully equivalent to original circular object.
Files readme.js or readme.txt repeat main things in compact form:
Addendum
criterion to identify circular refference
by processing we would have got pair of arrays
<uids> vs <values> - which are set of pairs <uid> vs <value> obtained
only for properties who are or objects or arrays. Some of them could refere
to another one, so called interlinked(interreference) properties.
Among them there are circular references - circular reference occures
when subproperty referes to some porperty of upper level
(e.g. o.a = o, or o.f.d.c = o.f or in the case of array
o = {a: 'a', b: [1, 2, o.b, 4]})
<uids> contains :
- or
uidas value of propertyuidof an object or
oUidparameter determined for array (array can't containuidproperty) of entity described by pair<uid>vs<value><values>contains :or object or array with primitive types' values (conditionally let's name them as array or object resolved)
or compound entity with properties' or elements's value as uid-like strings (something like
##c#4#...) ( compound entity whose elements or properties are `uids-like string)uid-likestring as value of some element or property is a mark of<circular refference>.The object having some property or subproperty( property of subobject ( subobject is a property whose value
TypeisObject) withuid-likevalue which itself is equal touid's value of this particular entity (value of property named'uid'or value of<uid>in pair<uid>vs<val>) is<circular refferenced>objectThe situation is more complicated when the object is an array ( array has no
uidproperty
but it has it in<uid>of<uids> vs <vals>pairs).In this case we need to analyse the
uidof object contained this array and detect whenuid-likevalue of this array's element coinsides withuidof containing array object or has last digit(s) inuid-likevalue of some element and the digital number is equal to the index of this element in array.
If array containing an element withuid-likevalue is<value>itself in<uids> vs <values>pair. The comparisson ofuid-likeelement's value should be provided with<uid>value of the pair
algorithm simply:
- we get
uidvalue - and looking for this value among values of all properties(elements)
and subrpoterties(subelements) (excluding ones with property
name
'uid') of all<vals>including and following after<val>pairing that<uid>
uid syntax
# character at the beginnig of uid( RegExp pattern /^#/)
is appropriate to object {...}
#c at the beginning of uid is appropriate to object
with property id( or im from I'm) value ='c' - {id:'c',...} or { im:'c',...}
each next # in uid string is appropriate consequent property name
for example
# means {} or [] or {...,uid:'#'}
#c means {id:'c',..,uid:'#c'} - instead of id property im (I'm)
could be used as well
#5 means [prim0,prim1,prim2,prim3,prim4,{},..] or
[prim0,prim1,prim2,prim3,prim4,[],..]
##bb means - {bb:{..},...} or {bb:[...],} property bb of object
without id property
#c#b means - {id:'c',b: {...,uid: '#c#b'},...,uid:'#c'} or
{ id:'c',b:[...],uid:'#c'}
#c#b#3 -
{id:'c',.., b: [1, 'a', {...,uid:'#c#b#3'},..],
...uid:'#c'}
or {id:'c',..,b:[1,'a',[...],..],...}
<uids> vs <values>
'#c' {id: 'c', c: '#', uid:'#c'}
'##d' {d: 'some_d', uid: '##d'}You are welcome!
Vladimir Uralov
v.url.node@gmail.com