Skip to content

Commit da983d1

Browse files
committed
feat: improve serialize-javascript
1 parent 3cf6f7b commit da983d1

File tree

1 file changed

+88
-48
lines changed

1 file changed

+88
-48
lines changed

test/utils/serialize-javascript.ts

Lines changed: 88 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ import { version } from './version';
2323
* 8. Respect `toJSON` and `fromJSON` method, if the object has a `toJSON` method, it will be called to
2424
* get the serialized value. If the object has a `fromJSON` method, it will be called with
2525
* serialized json to restore the original value.
26+
* 9. Make sure you trust the source of the serialized string, because the deserialization need to
27+
* evaluate script codes. A carefully crafted strings may embed malicious code, thus posing a
28+
* security threat.
2629
*/
2730
/**
2831
* Advantages:
@@ -597,8 +600,8 @@ function generateDeserializationCode(result: SerializedResult, options: Internal
597600
const symbolKeySuffixRegExp = new RegExp('${escapeRegExp(SymbolKeySuffixRegExp, { escapeTwice: isPrinting })}$');
598601
599602
// 1. Should be the first step.
600-
// Restore to the original types.
601-
restoreOriginalTypes(deserializeResult, types);
603+
// Restore to the original types, except the root object.
604+
restoreOriginalTypes(deserializeResult, types.filter((t) => t.path.length > 0));
602605
603606
// 2. Should be before restoreSymbolKeys, because the symbol-strings may be broken to Symbols.
604607
// Restore patches
@@ -613,62 +616,89 @@ function generateDeserializationCode(result: SerializedResult, options: Internal
613616
// 4-2. Should be before restoreDescriptors, because related fields may be changed to readonly.
614617
// Restore references to solve circular dependencies
615618
restoreRefs(deserializeResult, refs);
616-
617-
// 5. Should be the last step.
619+
620+
// 5. Should be after restoreRefs.
618621
// Restore custom property descriptors
619622
restoreDescriptors(deserializeResult, descriptors);
620623
624+
// 6. Should be the last step.
625+
// Restore the root object type.
626+
if (types.some((t) => t.path.length === 0)) {
627+
const rootResult = restoreOriginalTypes(deserializeResult, types.filter((t) => t.path.length === 0));
628+
const newRoot = rootResult.root;
629+
if (refs.some((t) => t.from.length === 0)) {
630+
// Remap the refs to the root
631+
const rootRefs = refs.filter((t) => t.from.length === 0);
632+
rootRefs.forEach(({ path, from }) => {
633+
const parent = getParent(deserializeResult, path);
634+
const keyName = getLastKey(path);
635+
if (parent != null && keyName) {
636+
parent[keyName] = newRoot;
637+
}
638+
});
639+
}
640+
return newRoot;
641+
}
642+
621643
function restoreOriginalTypes(root, types = []) {
644+
const returnResult = {};
622645
// Apply types to the deserialized object
623646
types.forEach(({ path, type, metadata }) => {
624-
// todo: path = [] 时,下面的逻辑会有问题
625-
// todo: 测试支持BigInt64Array的序列化
626647
// todo: 支持URL、URLSearchParams、支持Buffer
627-
const keyName = getLastKey(path);
628-
const parent = getParent(root, path);
629648
const value = get(root, path);
630-
console.log('Restoring type:', type, 'at', path, 'with value:', value, parent[keyName] === value);
631-
if (value && parent && parent[keyName] === value) {
632-
if (type === 'Map' && typeof value === 'object') {
633-
// Convert array to Map
634-
const map = new Map();
635-
Object.keys(value).forEach((key) => {
636-
map.set(key, value[key]);
637-
});
638-
parent[keyName] = map;
639-
}
640-
else if (type === 'Set' && Array.isArray(value)) {
641-
// Convert array to Set
642-
const set = new Set(value);
643-
parent[keyName] = set;
644-
}
645-
else if ([${TypedArrays.map((t) => `'${t.name}'`).join(', ')}].includes(type) &&
646-
typeof globalThis[type] === 'function' &&
647-
Array.isArray(value)) {
648-
console.log('Creating TypedArray:', type, value);
649-
parent[keyName] = new globalThis[type](value);
650-
}
651-
else if (type === 'ArrayBuffer' && typeof ArrayBuffer === 'function' && typeof Uint8Array === 'function' && Array.isArray(value)) {
652-
const buffer = new ArrayBuffer(value.length);
653-
const view = new Uint8Array(buffer);
654-
value.forEach((item, index) => {
655-
view[index] = item;
656-
});
657-
parent[keyName] = buffer;
658-
}
659-
else if (type === 'DataView' && typeof DataView === 'function' && typeof ArrayBuffer === 'function' && typeof Uint8Array === 'function' && Array.isArray(value)) {
660-
const buffer = new ArrayBuffer(value.length);
661-
const view = new Uint8Array(buffer);
662-
value.forEach((item, index) => {
663-
view[index] = item;
664-
});
665-
parent[keyName] = new DataView(buffer);
649+
let newResult;
650+
if (type === 'Map' && typeof value === 'object') {
651+
// Convert array to Map
652+
const map = new Map();
653+
Object.keys(value).forEach((key) => {
654+
map.set(key, value[key]);
655+
});
656+
newResult = map;
657+
}
658+
else if (type === 'Set' && Array.isArray(value)) {
659+
// Convert array to Set
660+
const set = new Set(value);
661+
newResult = set;
662+
}
663+
else if ([${TypedArrays.map((t) => `'${t.name}'`).join(', ')}].includes(type) &&
664+
typeof globalThis[type] === 'function' &&
665+
Array.isArray(value)) {
666+
newResult = new globalThis[type](value);
667+
}
668+
else if (type === 'ArrayBuffer' && typeof ArrayBuffer === 'function' && typeof Uint8Array === 'function' && Array.isArray(value)) {
669+
const buffer = new ArrayBuffer(value.length);
670+
const view = new Uint8Array(buffer);
671+
value.forEach((item, index) => {
672+
view[index] = item;
673+
});
674+
newResult = buffer;
675+
}
676+
else if (type === 'DataView' && typeof DataView === 'function' && typeof ArrayBuffer === 'function' && typeof Uint8Array === 'function' && Array.isArray(value)) {
677+
const buffer = new ArrayBuffer(value.length);
678+
const view = new Uint8Array(buffer);
679+
value.forEach((item, index) => {
680+
view[index] = item;
681+
});
682+
newResult = new DataView(buffer);
683+
}
684+
else if (type === 'Blob' && typeof Blob === 'function' && Array.isArray(value)) {
685+
newResult = new Blob(value, { type: metadata && metadata.type ? metadata.type : '' });
686+
}
687+
688+
if (newResult) {
689+
if (path.length === 0) {
690+
returnResult.root = newResult;
666691
}
667-
else if (type === 'Blob' && typeof Blob === 'function' && Array.isArray(value)) {
668-
parent[keyName] = new Blob(value, { type: metadata && metadata.type ? metadata.type : '' });
692+
else {
693+
const keyName = getLastKey(path);
694+
const parent = getParent(root, path);
695+
if (parent) {
696+
parent[keyName] = newResult;
697+
}
669698
}
670699
}
671700
});
701+
return returnResult;
672702
}
673703
674704
function restorePatches(root, patches = []) {
@@ -749,7 +779,10 @@ function generateDeserializationCode(result: SerializedResult, options: Internal
749779
}
750780
751781
function getParent(root, path) {
752-
return path && path.length ? get(root, path.slice(0, -1)) : null;
782+
if (path.length <= 1) {
783+
return root;
784+
}
785+
return get(root, path.slice(0, -1));
753786
}
754787
755788
function isSymbolFieldName(key) {
@@ -834,7 +867,14 @@ export function pickPrototype(
834867
const target: Record<string | symbol, any> = Object.create(null);
835868
const ignoredKeys = [preserveClassConstructor ? undefined : 'constructor'].filter(Boolean) as (string | symbol)[];
836869
let proto = Object.getPrototypeOf(source);
837-
while (proto != null && proto !== Object.prototype && proto !== Array.prototype) {
870+
while (
871+
proto != null &&
872+
proto !== Object.prototype &&
873+
proto !== Array.prototype &&
874+
proto !== Function.prototype &&
875+
proto !== Map.prototype &&
876+
proto !== Set.prototype
877+
) {
838878
const protoKeys = getFullKeys(proto);
839879
for (const key of protoKeys) {
840880
if (!(key in target) && !ignoredKeys.includes(key)) {

0 commit comments

Comments
 (0)