Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@ungap/structured-clone

Package Overview
Dependencies
Maintainers
1
Versions
21
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ungap/structured-clone - npm Package Compare versions

Comparing version 0.2.3 to 0.3.0

3

cjs/index.js
'use strict';
const {deserialize} = require('./deserialize.js');
const {serialize} = require('./serialize.js');
const dflt = {transfer: []};

@@ -19,5 +18,5 @@ /**

structuredClone :
(any, options = dflt) => deserialize(serialize(any, options));
(any, options) => deserialize(serialize(any, options));
exports.deserialize = deserialize;
exports.serialize = serialize;

@@ -6,76 +6,140 @@ 'use strict';

const EMPTY = '';
const {toString} = {};
const {keys} = Object;
const _serialize = (value, $, _) => {
const typeOf = value => {
const type = typeof value;
if (type !== 'object' || !value)
return [PRIMITIVE, type];
const asString = toString.call(value).slice(8, -1);
switch (asString) {
case 'Array':
return [ARRAY, EMPTY];
case 'Object':
return [OBJECT, EMPTY];
case 'Date':
return [DATE, EMPTY];
case 'RegExp':
return [REGEXP, EMPTY];
case 'Map':
return [MAP, EMPTY];
case 'Set':
return [SET, EMPTY];
}
if (asString.includes('Array'))
return [ARRAY, asString];
if (asString.includes('Error'))
return [ERROR, asString];
return [OBJECT, asString];
};
const shouldSkip = ([TYPE, type]) => (
TYPE === PRIMITIVE &&
(type === 'function' || type === 'symbol')
);
const as = (out, value, $, _) => {
const index = _.push(out) - 1;
$.set(value, index);
return index;
};
const pair = (lossy, value, $, _) => {
if ($.has(value))
return $.get(value);
const index = _.push(value) - 1;
$.set(value, index);
let [TYPE, type] = typeOf(value);
switch (TYPE) {
case PRIMITIVE: {
let entry = value;
switch (type) {
case 'bigint':
TYPE = BIGINT;
entry = value.toString();
break;
case 'function':
case 'symbol':
if (!lossy)
throw new TypeError('unable to serialize ' + type);
entry = null;
break;
}
return as([TYPE, entry], value, $, _);
}
case ARRAY: {
if (type)
return as([type, [...value]], value, $, _);
const as = serialized => {
_[index] = serialized;
return index;
};
switch (typeof value) {
case 'object':
if (value !== null) {
const type = toString.call(value).slice(8, -1);
const arr = [];
const index = as([TYPE, arr], value, $, _);
for (const entry of value)
arr.push(pair(lossy, entry, $, _));
return index;
}
case OBJECT: {
if (type) {
switch (type) {
case 'Array':
return as([ARRAY, value.map(entry => _serialize(entry, $, _))]);
case 'Object': {
const entries = [];
for (const key of keys(value))
entries.push([_serialize(key, $, _), _serialize(value[key], $, _)]);
return as([OBJECT, entries]);
}
case 'Date':
return as([DATE, value.toISOString()]);
case 'RegExp': {
const {source, flags} = value;
return as([REGEXP, {source, flags}]);
}
case 'Map': {
const entries = [];
for (const [key, entry] of value)
entries.push([_serialize(key, $, _), _serialize(entry, $, _)]);
return as([MAP, entries]);
}
case 'Set': {
const values = [];
for (const entry of value)
values.push(_serialize(entry, $, _));
return as([SET, values]);
}
case 'BigInt':
return as([type, value.toString()], value, $, _);
case 'Boolean':
case 'Number':
case 'String':
return as([type, value.valueOf()]);
case 'BigInt':
return as([type, value.toString()]);
return as([type, value.valueOf()], value, $, _);
}
}
if (type.includes('Array'))
return as([type, [...value]]);
const entries = [];
const index = as([TYPE, entries], value, $, _);
for (const key of keys(value)) {
if (lossy && shouldSkip(typeOf(value[key])))
continue;
if (type.includes('Error')) {
const {message} = value;
return as([ERROR, {name: type, message}]);
}
entries.push([
pair(lossy, key, $, _),
pair(lossy, value[key], $, _)
]);
}
return index;
}
case DATE:
return as([TYPE, value.toISOString()], value, $, _);
case REGEXP: {
const {source, flags} = value;
return as([TYPE, {source, flags}], value, $, _);
}
case MAP: {
const entries = [];
const index = as([TYPE, entries], value, $, _);
for (const [key, entry] of value) {
if (lossy && (shouldSkip(typeOf(key)) || shouldSkip(typeOf(entry))))
continue;
throw new TypeError;
entries.push([
pair(lossy, key, $, _),
pair(lossy, entry, $, _)
]);
}
case 'boolean':
case 'number':
case 'string':
case 'undefined':
return as([PRIMITIVE, value]);
case 'bigint':
return as([BIGINT, value.toString()]);
default:
throw new TypeError;
return index;
}
case SET: {
const entries = [];
const index = as([TYPE, entries], value, $, _);
for (const entry of value) {
if (lossy && shouldSkip(typeOf(entry)))
continue;
entries.push(pair(lossy, entry, $, _));
}
return index;
}
}
const {message} = value;
return as([TYPE, {name: type, message}], value, $, _);
};

@@ -90,8 +154,11 @@

* @param {any} serializable a serializable value.
* @param {{lossy?: boolean}?} options an object with a `lossy` property that,
* if `true`, will not throw errors on incompatible types, and behave more
* like JSON stringify would behave. Symbol and Function will be discarded.
* @returns {Record[]}
*/
const serialize = serializable => {
const serialize = (serializable, options = {}) => {
const _ = [];
return _serialize(serializable, new Map, _), _;
return pair(!!options.lossy, serializable, new Map, _), _;
};
exports.serialize = serialize;
'use strict';
let i = 0;
const PRIMITIVE = i++;
const PRIMITIVE = 0;
exports.PRIMITIVE = PRIMITIVE;
const ARRAY = i++;
const ARRAY = 1;
exports.ARRAY = ARRAY;
const OBJECT = i++;
const OBJECT = 2;
exports.OBJECT = OBJECT;
const DATE = i++;
const DATE = 3;
exports.DATE = DATE;
const REGEXP = i++;
const REGEXP = 4;
exports.REGEXP = REGEXP;
const MAP = i++;
const MAP = 5;
exports.MAP = MAP;
const SET = i++;
const SET = 6;
exports.SET = SET;
const ERROR = i++;
const ERROR = 7;
exports.ERROR = ERROR;
const BIGINT = i++;
const BIGINT = 8;
exports.BIGINT = BIGINT;
// export const SYMBOL = 9;
import {deserialize} from './deserialize.js';
import {serialize} from './serialize.js';
const dflt = {transfer: []};

@@ -18,4 +17,4 @@ /**

structuredClone :
(any, options = dflt) => deserialize(serialize(any, options));
(any, options) => deserialize(serialize(any, options));
export {deserialize, serialize};

@@ -7,76 +7,140 @@ import {

const EMPTY = '';
const {toString} = {};
const {keys} = Object;
const _serialize = (value, $, _) => {
const typeOf = value => {
const type = typeof value;
if (type !== 'object' || !value)
return [PRIMITIVE, type];
const asString = toString.call(value).slice(8, -1);
switch (asString) {
case 'Array':
return [ARRAY, EMPTY];
case 'Object':
return [OBJECT, EMPTY];
case 'Date':
return [DATE, EMPTY];
case 'RegExp':
return [REGEXP, EMPTY];
case 'Map':
return [MAP, EMPTY];
case 'Set':
return [SET, EMPTY];
}
if (asString.includes('Array'))
return [ARRAY, asString];
if (asString.includes('Error'))
return [ERROR, asString];
return [OBJECT, asString];
};
const shouldSkip = ([TYPE, type]) => (
TYPE === PRIMITIVE &&
(type === 'function' || type === 'symbol')
);
const as = (out, value, $, _) => {
const index = _.push(out) - 1;
$.set(value, index);
return index;
};
const pair = (lossy, value, $, _) => {
if ($.has(value))
return $.get(value);
const index = _.push(value) - 1;
$.set(value, index);
let [TYPE, type] = typeOf(value);
switch (TYPE) {
case PRIMITIVE: {
let entry = value;
switch (type) {
case 'bigint':
TYPE = BIGINT;
entry = value.toString();
break;
case 'function':
case 'symbol':
if (!lossy)
throw new TypeError('unable to serialize ' + type);
entry = null;
break;
}
return as([TYPE, entry], value, $, _);
}
case ARRAY: {
if (type)
return as([type, [...value]], value, $, _);
const as = serialized => {
_[index] = serialized;
return index;
};
switch (typeof value) {
case 'object':
if (value !== null) {
const type = toString.call(value).slice(8, -1);
const arr = [];
const index = as([TYPE, arr], value, $, _);
for (const entry of value)
arr.push(pair(lossy, entry, $, _));
return index;
}
case OBJECT: {
if (type) {
switch (type) {
case 'Array':
return as([ARRAY, value.map(entry => _serialize(entry, $, _))]);
case 'Object': {
const entries = [];
for (const key of keys(value))
entries.push([_serialize(key, $, _), _serialize(value[key], $, _)]);
return as([OBJECT, entries]);
}
case 'Date':
return as([DATE, value.toISOString()]);
case 'RegExp': {
const {source, flags} = value;
return as([REGEXP, {source, flags}]);
}
case 'Map': {
const entries = [];
for (const [key, entry] of value)
entries.push([_serialize(key, $, _), _serialize(entry, $, _)]);
return as([MAP, entries]);
}
case 'Set': {
const values = [];
for (const entry of value)
values.push(_serialize(entry, $, _));
return as([SET, values]);
}
case 'BigInt':
return as([type, value.toString()], value, $, _);
case 'Boolean':
case 'Number':
case 'String':
return as([type, value.valueOf()]);
case 'BigInt':
return as([type, value.toString()]);
return as([type, value.valueOf()], value, $, _);
}
}
if (type.includes('Array'))
return as([type, [...value]]);
const entries = [];
const index = as([TYPE, entries], value, $, _);
for (const key of keys(value)) {
if (lossy && shouldSkip(typeOf(value[key])))
continue;
if (type.includes('Error')) {
const {message} = value;
return as([ERROR, {name: type, message}]);
}
entries.push([
pair(lossy, key, $, _),
pair(lossy, value[key], $, _)
]);
}
return index;
}
case DATE:
return as([TYPE, value.toISOString()], value, $, _);
case REGEXP: {
const {source, flags} = value;
return as([TYPE, {source, flags}], value, $, _);
}
case MAP: {
const entries = [];
const index = as([TYPE, entries], value, $, _);
for (const [key, entry] of value) {
if (lossy && (shouldSkip(typeOf(key)) || shouldSkip(typeOf(entry))))
continue;
throw new TypeError;
entries.push([
pair(lossy, key, $, _),
pair(lossy, entry, $, _)
]);
}
case 'boolean':
case 'number':
case 'string':
case 'undefined':
return as([PRIMITIVE, value]);
case 'bigint':
return as([BIGINT, value.toString()]);
default:
throw new TypeError;
return index;
}
case SET: {
const entries = [];
const index = as([TYPE, entries], value, $, _);
for (const entry of value) {
if (lossy && shouldSkip(typeOf(entry)))
continue;
entries.push(pair(lossy, entry, $, _));
}
return index;
}
}
const {message} = value;
return as([TYPE, {name: type, message}], value, $, _);
};

@@ -91,7 +155,10 @@

* @param {any} serializable a serializable value.
* @param {{lossy?: boolean}?} options an object with a `lossy` property that,
* if `true`, will not throw errors on incompatible types, and behave more
* like JSON stringify would behave. Symbol and Function will be discarded.
* @returns {Record[]}
*/
export const serialize = serializable => {
export const serialize = (serializable, options = {}) => {
const _ = [];
return _serialize(serializable, new Map, _), _;
return pair(!!options.lossy, serializable, new Map, _), _;
};

@@ -1,11 +0,10 @@

let i = 0;
export const PRIMITIVE = i++;
export const ARRAY = i++;
export const OBJECT = i++;
export const DATE = i++;
export const REGEXP = i++;
export const MAP = i++;
export const SET = i++;
export const ERROR = i++;
export const BIGINT = i++;
export const PRIMITIVE = 0;
export const ARRAY = 1;
export const OBJECT = 2;
export const DATE = 3;
export const REGEXP = 4;
export const MAP = 5;
export const SET = 6;
export const ERROR = 7;
export const BIGINT = 8;
// export const SYMBOL = 9;
{
"name": "@ungap/structured-clone",
"version": "0.2.3",
"version": "0.3.0",
"description": "A structuredClone polyfill",

@@ -5,0 +5,0 @@ "main": "./cjs/index.js",

@@ -37,1 +37,23 @@ # structuredClone polyfill

```
### Extra Features
There is no middle-ground between the structured clone algorithm and JSON:
* JSON is more relaxed about incompatible values: it just ignores these
* Structured clone is inflexible regarding incompatible values, yet it makes specialized instances impossible to reconstruct, plus it doesn't offer any helper, such as `toJSON()`, to make serialization possible, or better, with specific cases
This module specialized `serialize` export offers, within the optional extra argument, a **lossy** property to avoid throwing when incompatible types are found down the road (function, symbol, ...), so that it is possible to send with less worrying about thrown errors.
```js
// as default export
import structuredClone from '@ungap/structured-clone';
const cloned = structuredClone({
method() {
// ignored, won't be cloned
},
special: Symbol('also ignored')
});
```
The behavior is the same found in *JSON* when it comes to *Array*, so that unsupported values will result as `null` placeholders instead.
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc