It's so hard to be what it's all about! When your outsides in. Insides out and your downsides up. Yea! Don't you want to twist and shout when you're inside out.
The Travelling Wilburys
The Wilburys know what it takes to stringify Maps, Sets, ArrayBuffers, and DataViews. They have to be turned inside out. A stringification algorithm, which transverses subobjects/subproperties in a preorder transversal, only naturally deals with external properites, not internal properties. So the internal properties are turned inside out to become external properties that the algorithm can deal with.
We invite the clever reader to show how to turn an ArrayBuffer inside out.
If you want to attempt to understand the source code at Stringify.js, then the article Deep Copies with Circular References is a prerequisite since it introduces The Basic and Powerful Preorder Transversal which is used in Stringify.js, parseDataString.js, deepCopyData.js, and deepCopy.js. It's a general technique that should be studied by the sorts of programmers who study those sorts of things.
The title and following paragraphs are shameless promotion. But it needs to be made clear that you should be excited because our serialization/deserialization goes way beyond JSON into actually serializing objects that are class instances in such a way that deserialization produces a deep copy of the object with equivalent but separate internal state.
The mundane level also goes way beyond JSON. JSON is rather limited in the types of objects that can be serialized. We serialize all* built in data types with infinite complexity. E.g., Sets whose members are Maps whose keys are ArrayBuffers and values are typed arrays. The Set can also have properties that are DataViews whose properties are Maps whose properties are Sets whose properties are ArrayBuffers.
(*) It is impossible for any system to serialize symbols, WeakSets, and WeakMaps because there is no way for the programmer to read their internal state.
Serialization is built on stringification. In our system, stringification is just for display/dumping. Our stringification, not originally intended for serialization, was easy to modify for serialization. Our dtype() function wasn't designed for serialization either, but its detailed design gave it an automatically small but important role in checking that certain objects are suitable for serialization.
Both serialization/deserialization and stringification handles circular and duplicate references. Stringification and hence serialization encodes such references so that deserialization can decode them and reproduce the circular and duplicate references.
Two nodes in the object tree of x that are equal are circular references if one is the ancestor of the other. Otherwise they are duplicate references. Naive algorithms for various tasks will loop infinitely with circular references, but not with duplicate references.
Serialization/Deserialization preserves property descriptors by encoding/decoding them. For example if Y is a deep copy of X through serializtion/deserialization then the property descriptor of Y.a.b.c in Y.a.b is the same as the property descriptor of X.a.b.c in X.a.b.
The following sections discuss stringification. However, after that, be sure to read the serialization appendix because it introduces terms necessary in two subsequent articles. One is the article on parseDataString(), which is our deserialization function. The other is the article on serializing classes.
Defalt values are underlined and the question mark indicates existence is optional.
If false, then the value returned by the getter is written and processing continues for any of its child nodes.
There's actually more attributes that param can have, but they are for internal use in our library.
First top referes to the top node, in this case z.
We first read that dtype(top) is "Object". This doesn't mean that top can be any old object. It precisely means that the internal prototoype of top is Object.prototype. top is one degree away from Object.prototype. Likewise top.M, top.M.a, and top.N all have "Object" as their dtype() and hence all are one degree away from Object.prototype.
The @ symbol indicates a circular reference. The right side of top.M.a.b indicates that top.M.a.b is a circular reference to top.M an "Object" named M two levels up. That is top.M.a.b and top.M are the same.
The # symbol indicates a dupiclate reference. top.N is a duplicate reference to top.M.a. That is top.N and top.M.a are the same.
The dtype of z is "Object(3)" meaning that z is an object three degrees from Object.prototype. That is z.__proto__.__proto__.___proto__ is Object.prototype.
Since it is impossible to read Symbols, we can only notate that z.g is a Symbol.
The elementary classes are Boolean, Number, String, Date, and RegExp. Stringifications of their class instances all behave the same.
We read that the dtype of a is Boolean. That means a is a Boolean class instance. That it has a value of true is indicated. Its properties are indicated inside braces. If there were no properties, the braces would be empty.
The dtype of b is Number, so b is a Number class instance. It has one property z which the the a we've seen before. To the right of z: is the stringification of a.
The dtype of c is String, so c is a String class instance. Its value is indicated as "cat". It has no properties other than the length, which we omit since it isn't needed for informational purpposes or for deserializtion.
The dtype of d is Date, so c is a Date class instance. Its value is indicated next. d has no properties.
The dtype of e is RegExp, so e is a RegExp class instance. Its value is indicated next. JavaScript gives e the lastIndex property. Our added property a is shown next.
The stringification of Arrays behave in a similar manner to stringification of literal objects.
The dtype of x is Array, so x is a class instance of Array. Of particular note, the stringification of x.a is shown. The dtype of x.a is "Boolean[Object(2)]" meaning that x is an instance of Boolean but not a class instance since it is 2 degrees away from Boolean.prototype. It follows type(x.a) = "Object", which is written for emphasis.
There are nine typed arrays: Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array. The stringifications all act the same so we look at Uint8Array.
By now the reader shouldn't have any trouble understanding the stringification of a Map.
It is not possible to properly stringify a WeakMap since there is no way to obtain its keys. Thus we stringify by notating that a WeakMap was found with no further details other than properties that were added.
OK! This is easy to read.
It is not possible to properly stringify a WeakSet since there is no way to iterate through its members. Thus we stringify by notating that a WeakSet was found with no further details other than properties that were added. Stringification behaves as it did with WeakMap.
There are four functions that wrap stringify()
The following code shows the first three in action. For serializeClassData() see the class serialization/deserialization article.
If the dtype of an element contains the symbol '[' then the element is not simple-data.
If x and every subobject of x is not a symbol, WeakSet, WeakMap or Function, then x is simple data exactly when dtype(y) does not contain the symbol '[', for y = x, and all subobjects y of x.
The author could not come up with a better term than FData.
Warning! The results of serializeFData() can only be deserialized by finding an approprite evaluator, which might be difficult when actually possible.
Well OK! See parseDataString Test.js file for serialization and deserialization of fdata. Go to the end of the file and look for TestCircularFunctions() and TestCircularGets().