Optimize Struct from Struct construction (#4128)

* Optimize Struct from Map constrcution

* Update packages/types/src/metadata/MetadataVersioned.ts

* CHANGELOG

* Only shortcut Struct

* tests
This commit is contained in:
Jaco
2021-10-30 17:50:37 +03:00
committed by GitHub
parent 5827d298ad
commit 2c86e9347b
5 changed files with 81 additions and 78 deletions
+1 -5
View File
@@ -111,15 +111,11 @@ describe('Struct', (): void => {
});
it('decodes from a Map input', (): void => {
const input = new Struct(registry, {
a: U32,
txt: Text
}, { a: 42, txt: 'fubar' });
const s = new Struct(registry, {
txt: Text,
foo: U32,
bar: U32
}, input);
}, new Map<string, unknown>([['a', 42], ['txt', 'fubar']]));
expect(s.toString()).toEqual('{"txt":"fubar","foo":0,"bar":0}');
});
+71 -65
View File
@@ -11,75 +11,77 @@ import { compareMap, decodeU8a, mapToTypeMap } from './utils';
type TypesDef<T = Codec> = Record<string, string | Constructor<T>>;
/** @internal */
function decodeStructFromObject (registry: Registry, Types: ConstructorDef, value: any, jsonMap: Map<any, string>): Record<string, Codec> {
function decodeStructFromObject (registry: Registry, Types: ConstructorDef, value: any, jsonMap: Map<any, string>): Iterable<[string, Codec]> {
let jsonObj: Record<string, unknown> | undefined;
const inputKeys = Object.keys(Types);
assert(!Array.isArray(value) || value.length === inputKeys.length, () => `Struct: Unable to map ${stringify(value)} array to object with known keys ${inputKeys.join(', ')}`);
return inputKeys.reduce<Record<string, Codec>>((raw, key, index) => {
// The key in the JSON can be snake_case (or other cases), but in our
// Types, result or any other maps, it's camelCase
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const jsonKey = (jsonMap.get(key as any) && !value[key])
? jsonMap.get(key as any)
: key;
const Type = Types[key];
try {
if (Array.isArray(value)) {
// TS2322: Type 'Codec' is not assignable to type 'T[keyof S]'.
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
(raw as any)[key] = value[index] instanceof Type
? value[index]
: new Type(registry, value[index]);
} else if (value instanceof Map) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const mapped = value.get(jsonKey);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(raw as any)[key] = mapped instanceof Type
? mapped
: new Type(registry, mapped);
} else if (isObject(value)) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
let assign = value[jsonKey as string];
if (isUndefined(assign)) {
if (isUndefined(jsonObj)) {
jsonObj = Object.entries(value).reduce((all: Record<string, any>, [key, value]): Record<string, any> => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
all[stringCamelCase(key)] = value;
return all;
}, {});
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
assign = jsonObj[jsonKey as string];
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
(raw as any)[key] = assign instanceof Type
? assign
: new Type(registry, assign);
} else {
throw new Error(`Cannot decode value ${stringify(value)} (typeof ${typeof value}), expected an input object with known keys`);
}
} catch (error) {
let type = Type.name;
return Object.entries(
inputKeys.reduce<Record<string, Codec>>((raw, key, index) => {
// The key in the JSON can be snake_case (or other cases), but in our
// Types, result or any other maps, it's camelCase
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const jsonKey = (jsonMap.get(key as any) && !value[key])
? jsonMap.get(key as any)
: key;
const Type = Types[key];
try {
type = new Type(registry).toRawType();
if (Array.isArray(value)) {
// TS2322: Type 'Codec' is not assignable to type 'T[keyof S]'.
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
(raw as any)[key] = value[index] instanceof Type
? value[index]
: new Type(registry, value[index]);
} else if (value instanceof Map) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const mapped = value.get(jsonKey);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(raw as any)[key] = mapped instanceof Type
? mapped
: new Type(registry, mapped);
} else if (isObject(value)) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
let assign = value[jsonKey as string];
if (isUndefined(assign)) {
if (isUndefined(jsonObj)) {
jsonObj = Object.entries(value).reduce((all: Record<string, any>, [key, value]): Record<string, any> => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
all[stringCamelCase(key)] = value;
return all;
}, {});
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
assign = jsonObj[jsonKey as string];
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
(raw as any)[key] = assign instanceof Type
? assign
: new Type(registry, assign);
} else {
throw new Error(`Cannot decode value ${stringify(value)} (typeof ${typeof value}), expected an input object with known keys`);
}
} catch (error) {
// ignore
let type = Type.name;
try {
type = new Type(registry).toRawType();
} catch (error) {
// ignore
}
throw new Error(`Struct: failed on ${jsonKey as string}: ${type}:: ${(error as Error).message}`);
}
throw new Error(`Struct: failed on ${jsonKey as string}: ${type}:: ${(error as Error).message}`);
}
return raw;
}, {});
return raw;
}, {})
);
}
/**
@@ -98,7 +100,7 @@ function decodeStructFromObject (registry: Registry, Types: ConstructorDef, valu
* @param jsonMap
* @internal
*/
function decodeStruct (registry: Registry, Types: ConstructorDef, value: unknown, jsonMap: Map<any, string>): [Record<string, Codec>, number] {
function decodeStruct (registry: Registry, Types: ConstructorDef, value: unknown, jsonMap: Map<any, string>): [Iterable<[string, Codec]>, number] {
if (isHex(value)) {
return decodeStruct(registry, Types, hexToU8a(value), jsonMap);
} else if (isU8a(value)) {
@@ -107,13 +109,17 @@ function decodeStruct (registry: Registry, Types: ConstructorDef, value: unknown
// Transform array of values to {key: value} mapping
return [
keys.reduce<Record<string, Codec>>((raw, key, index) => {
raw[key] = values[index];
Object.entries(
keys.reduce<Record<string, Codec>>((raw, key, index) => {
raw[key] = values[index];
return raw;
}, {}),
return raw;
}, {})
),
decodedLength
];
} else if (value instanceof Struct) {
return [value as Iterable<[string, Codec]>, 0];
}
// We assume from here that value is a JS object (Array, Map, Object)
@@ -151,7 +157,7 @@ export class Struct<
constructor (registry: Registry, Types: S, value?: V | Map<unknown, unknown> | unknown[] | string | null, jsonMap: Map<keyof S, string> = new Map()) {
const [decoded, decodedLength] = decodeStruct(registry, mapToTypeMap(registry, Types), value, jsonMap);
super(Object.entries(decoded) as [keyof S, Codec][]);
super(decoded);
this.registry = registry;
this.#initialU8aLength = decodedLength;
@@ -14,19 +14,19 @@ export type HasherFunction = (data: HasherInput) => Uint8Array;
const DEFAULT_FN = (data: HasherInput): Uint8Array => xxhashAsU8a(data, 128);
const HASHERS: Record<keyof typeof AllHashers, HasherFunction> = {
Blake2_128: (data: HasherInput): Uint8Array => // eslint-disable-line camelcase
Blake2_128: (data: HasherInput) => // eslint-disable-line camelcase
blake2AsU8a(data, 128),
Blake2_128Concat: (data: HasherInput): Uint8Array => // eslint-disable-line camelcase
Blake2_128Concat: (data: HasherInput) => // eslint-disable-line camelcase
u8aConcat(blake2AsU8a(data, 128), u8aToU8a(data)),
Blake2_256: (data: HasherInput): Uint8Array => // eslint-disable-line camelcase
Blake2_256: (data: HasherInput) => // eslint-disable-line camelcase
blake2AsU8a(data, 256),
Identity: (data: HasherInput): Uint8Array =>
Identity: (data: HasherInput) =>
u8aToU8a(data),
Twox128: (data: HasherInput): Uint8Array =>
Twox128: (data: HasherInput) =>
xxhashAsU8a(data, 128),
Twox256: (data: HasherInput): Uint8Array =>
Twox256: (data: HasherInput) =>
xxhashAsU8a(data, 256),
Twox64Concat: (data: HasherInput): Uint8Array =>
Twox64Concat: (data: HasherInput) =>
u8aConcat(xxhashAsU8a(data, 64), u8aToU8a(data))
};
@@ -13,7 +13,7 @@ export function getStorage (registry: Registry): Storage {
substrate: Object
.entries(substrate)
.reduce((storage: Record<string, StorageEntry>, [key, fn]): Record<string, StorageEntry> => {
(storage as Record<string, unknown>)[key] = fn(registry);
storage[key] = fn(registry);
return storage;
}, {})