uncycle
v0.5.5
Published
JSON.stringify objects with circular references and RegExp properties and/or then JSON.parse into original objects. Supplies handler doing object with circular references and RepExp serializable. Provides Replacer and Reviver functions for JSON.stringify
Downloads
10
Maintainers
Readme
unCycle - handler to serialize and clone objects with circular references and RegExp
introduction
unCycle
is 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
.unCycle
module provides a handler makingJSON
capable
to stringify and parse objects- with circular references and
- having
RegExp
objects 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 object
the 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
uncycle
for 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 uncycle
After 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_modules
has been created in~\your-proj-dir\
dir with two subfoldersuncycle
andregstr
:
~\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 theuncycle
dir
cd .\node_modules\uncycle\
npm run test
See 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 explain
ornpm run readme
- or both
- or read explain.txt file in some text editor
- to test type
npm run test
for regstr
npm run explain
npm run explain-ciph
npm run explain-deciph
- to test type
npm run test
Algorithm and examples
** ---- unCycle module ---- **.
unCycle
is a handler who supplies methods for:
ciphering and deciphering objects and arrays with circular references and
RegExp
properties enablingJSON
object to stringify and parse them back
by means ofJSON.stringify()
andJSON.parse()
methods and/orprovides
replacer
andreviver
functions (second parameter ofJSON.stringify()
andJSON.parse()
method) to stringify and parse objects and arrays with circular references andRegExp
properties.
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,
oU[uid]` 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 - digital
the 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.reviver
permiting parseoj
directly usingJSON.parse
with 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 arrayo = {a: 'a', b: [1, 2, o.b, 4]})
<uids>
contains :
- or
uid
as value of propertyuid
of an object - or
oUid
parameter determined for array (array can't containuid
property) 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-like
string 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 Type
is Object
) with
uid-like
value which itself is equal to uid
's value of this particular
entity (value of property named 'uid'
or
value of <uid>
in pair <uid>
vs <val>
)
is <circular refferenced>
object
The situation is more complicated when the object is an array
( array has no uid
property
but it has it in <uid>
of <uids> vs <vals>
pairs).
In this case we need to analyse the uid
of object contained
this array and detect
when uid-like
value of this array's element coinsides with uid
of
containing array object
or has last digit(s) in uid-like
value of some element and the digital
number is equal to the index of
this element in array.
If array containing an element with uid-like
value is <value>
itself
in <uids> vs <values>
pair. The comparisson of uid-like
element's
value should be provided with <uid>
value of the pair
algorithm simply:
- we get
uid
value - 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
[email protected]