Skip to content

Commit ff1cd9f

Browse files
committed
Fall back to match registered type by name
1 parent 898fbe0 commit ff1cd9f

File tree

5 files changed

+83
-7
lines changed

5 files changed

+83
-7
lines changed

yarn-project/foundation/src/json-rpc/class_converter.ts

+18-4
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,8 @@ export class ClassConverter {
158158
* @returns If it is a registered class.
159159
*/
160160
isRegisteredClass(obj: any) {
161-
return this.toName.has(obj);
161+
const name = obj.prototype.constructor.name;
162+
return this.toName.has(obj) || this.isRegisteredClassName(name);
162163
}
163164
/**
164165
* Convert a JSON-like object to a class object.
@@ -182,10 +183,23 @@ export class ClassConverter {
182183
* @returns The class object.
183184
*/
184185
toJsonObj(classObj: any): JsonEncodedClass | StringEncodedClass {
185-
const result = this.toName.get(classObj.constructor);
186-
assert(result, `Could not find class in lookup.`);
187-
const [type, encoding] = result;
186+
const { type, encoding } = this.lookupObject(classObj);
188187
const data = encoding === 'string' ? classObj.toString() : classObj.toJSON();
189188
return { type: type!, data };
190189
}
190+
191+
/**
192+
* Loads the corresponding type for this class based on constructor first and constructor name if not found.
193+
* Constructor match works in the event of a minifier changing function names, and constructor name match
194+
* works in the event of duplicated instances of node modules being loaded (see #1826).
195+
* @param classObj - Object to lookup in the registered types.
196+
* @returns Registered type name and encoding.
197+
*/
198+
private lookupObject(classObj: any) {
199+
const nameResult = this.toName.get(classObj.constructor);
200+
if (nameResult) return { type: nameResult[0], encoding: nameResult[1] };
201+
const classResult = this.toClass.get(classObj.constructor.name);
202+
if (classResult) return { type: classObj.constructor.name, encoding: classResult[1] };
203+
throw new Error(`Could not find class ${classObj.constructor.name} in lookup.`);
204+
}
191205
}

yarn-project/foundation/src/json-rpc/convert.test.ts

+32
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { Buffer } from 'buffer';
22

33
import { ClassConverter } from './class_converter.js';
44
import { convertBigintsInObj, convertFromJsonObj, convertToJsonObj } from './convert.js';
5+
import { ToStringClass as ToStringClassA } from './fixtures/class_a.js';
6+
import { ToStringClass as ToStringClassB } from './fixtures/class_b.js';
57
import { TestNote } from './fixtures/test_state.js';
68

79
const TEST_BASE64 = 'YmFzZTY0IGRlY29kZXI=';
@@ -24,3 +26,33 @@ test('does not convert a string', () => {
2426
expect(convertBigintsInObj('hello')).toEqual('hello');
2527
expect(convertBigintsInObj({ msg: 'hello' })).toEqual({ msg: 'hello' });
2628
});
29+
30+
test('converts a registered class', () => {
31+
const cc = new ClassConverter({ ToStringClass: ToStringClassA });
32+
const obj = { content: new ToStringClassA('a', 'b') };
33+
const serialised = convertToJsonObj(cc, obj);
34+
const deserialised = convertFromJsonObj(cc, serialised) as { content: ToStringClassA };
35+
expect(deserialised.content).toBeInstanceOf(ToStringClassA);
36+
expect(deserialised.content.x).toEqual('a');
37+
expect(deserialised.content.y).toEqual('b');
38+
});
39+
40+
test('converts a class by name in the event of duplicate modules being loaded', () => {
41+
const cc = new ClassConverter({ ToStringClass: ToStringClassA });
42+
const obj = { content: new ToStringClassB('a', 'b') };
43+
const serialised = convertToJsonObj(cc, obj);
44+
const deserialised = convertFromJsonObj(cc, serialised) as { content: ToStringClassA };
45+
expect(deserialised.content).toBeInstanceOf(ToStringClassA);
46+
expect(deserialised.content.x).toEqual('a');
47+
expect(deserialised.content.y).toEqual('b');
48+
});
49+
50+
test('converts a class by constructor instead of name in the event of minified bundle', () => {
51+
const cc = new ClassConverter({ NotMinifiedToStringClassName: ToStringClassA });
52+
const obj = { content: new ToStringClassA('a', 'b') };
53+
const serialised = convertToJsonObj(cc, obj);
54+
const deserialised = convertFromJsonObj(cc, serialised) as { content: ToStringClassA };
55+
expect(deserialised.content).toBeInstanceOf(ToStringClassA);
56+
expect(deserialised.content.x).toEqual('a');
57+
expect(deserialised.content.y).toEqual('b');
58+
});

yarn-project/foundation/src/json-rpc/convert.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ export function convertFromJsonObj(cc: ClassConverter, obj: any): any {
7676
return newObj;
7777
}
7878
// Throw if this is a non-primitive class that was not registered
79-
if (typeof obj === 'object' && obj.constructor !== Object) {
80-
throw new Error(`Object ${obj.constructor.name} not registered for deserialisation`);
79+
if (typeof obj === 'object' && Object.getPrototypeOf(obj) === Object.getPrototypeOf({})) {
80+
throw new Error(`Class ${obj.constructor.name} not registered for deserialisation`);
8181
}
8282
// Leave alone, assume JSON primitive
8383
return obj;
@@ -122,7 +122,7 @@ export function convertToJsonObj(cc: ClassConverter, obj: any): any {
122122
return newObj;
123123
}
124124
// Throw if this is a non-primitive class that was not registered
125-
if (typeof obj === 'object' && obj.constructor !== Object) {
125+
if (typeof obj === 'object' && Object.getPrototypeOf(obj) === Object.getPrototypeOf({})) {
126126
throw new Error(`Object ${obj.constructor.name} not registered for serialisation`);
127127
}
128128
// Leave alone, assume JSON primitive
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* Test class for testing string converter.
3+
*/
4+
export class ToStringClass {
5+
constructor(/** A value */ public readonly x: string, /** Another value */ public readonly y: string) {}
6+
7+
toString(): string {
8+
return [this.x, this.y].join('-');
9+
}
10+
11+
static fromString(value: string) {
12+
const [x, y] = value.split('-');
13+
return new ToStringClass(x, y);
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* Test class for testing string converter.
3+
*/
4+
export class ToStringClass {
5+
constructor(/** A value */ public readonly x: string, /** Another value */ public readonly y: string) {}
6+
7+
toString(): string {
8+
return [this.x, this.y].join('-');
9+
}
10+
11+
static fromString(value: string) {
12+
const [x, y] = value.split('-');
13+
return new ToStringClass(x, y);
14+
}
15+
}

0 commit comments

Comments
 (0)