diff --git a/packages/types/src/codec/Struct.ts b/packages/types/src/codec/Struct.ts index b4a0e225eb..9a66d3b8a3 100644 --- a/packages/types/src/codec/Struct.ts +++ b/packages/types/src/codec/Struct.ts @@ -10,78 +10,64 @@ import { compareMap, decodeU8a, mapToTypeMap } from './utils'; type TypesDef = Record>; +function newEntry (registry: Registry, Type: Constructor, key: string, value: unknown): [string, Codec] { + return [ + key, + value instanceof Type + ? value + : new Type(registry, value) + ]; +} + /** @internal */ -function decodeStructFromObject (registry: Registry, Types: ConstructorDef, value: any, jsonMap: Map): Iterable<[string, Codec]> { +function decodeStructFromObject (registry: Registry, Types: ConstructorDef, value: any, jsonMap: Map): Iterable<[string, Codec]> { let jsonObj: Record | 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 Object.entries( - inputKeys.reduce>((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]; + return inputKeys.reduce<[string, Codec][]>((raw, key, index) => { + const jsonKey = jsonMap.get(key) || 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); + try { + if (Array.isArray(value)) { + raw.push(newEntry(registry, Type, key, value[index])); + } else if (value instanceof Map) { + raw.push(newEntry(registry, Type, key, jsonKey && (value as Map).get(jsonKey))); + } else if (isObject(value)) { + let assign = jsonKey && value[jsonKey] as unknown; - // 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, [key, value]): Record => { + all[stringCamelCase(key)] = value; - if (isUndefined(assign)) { - if (isUndefined(jsonObj)) { - jsonObj = Object.entries(value).reduce((all: Record, [key, value]): Record => { - // 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]; + return all; + }, {}); } - // 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`); + assign = jsonKey && jsonObj[jsonKey]; } + + raw.push(newEntry(registry, Type, key, 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; + + try { + type = new Type(registry).toRawType(); } catch (error) { - 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}`); + // ignore } - return raw; - }, {}) - ); + throw new Error(`Struct: failed on ${jsonKey}: ${type}:: ${(error as Error).message}`); + } + + return raw; + }, []); } /** @@ -100,7 +86,7 @@ function decodeStructFromObject (registry: Registry, Types: ConstructorDef, valu * @param jsonMap * @internal */ -function decodeStruct (registry: Registry, Types: ConstructorDef, value: unknown, jsonMap: Map): [Iterable<[string, Codec]>, number] { +function decodeStruct (registry: Registry, Types: ConstructorDef, value: unknown, jsonMap: Map): [Iterable<[string, Codec]>, number] { if (isHex(value)) { return decodeStruct(registry, Types, hexToU8a(value), jsonMap); } else if (isU8a(value)) { @@ -109,13 +95,11 @@ function decodeStruct (registry: Registry, Types: ConstructorDef, value: unknown // Transform array of values to {key: value} mapping return [ - Object.entries( - keys.reduce>((raw, key, index) => { - raw[key] = values[index]; + keys.reduce<[string, Codec][]>((raw, key, index) => { + raw.push([key, values[index]]); - return raw; - }, {}) - ), + return raw; + }, []), decodedLength ]; } else if (value instanceof Struct) { @@ -154,7 +138,7 @@ export class Struct< readonly #Types: ConstructorDef; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - constructor (registry: Registry, Types: S, value?: V | Map | unknown[] | string | null, jsonMap: Map = new Map()) { + constructor (registry: Registry, Types: S, value?: V | Map | unknown[] | string | null, jsonMap: Map = new Map()) { const [decoded, decodedLength] = decodeStruct(registry, mapToTypeMap(registry, Types), value, jsonMap); super(decoded); @@ -165,7 +149,7 @@ export class Struct< this.#Types = mapToTypeMap(registry, Types); } - public static with (Types: S, jsonMap?: Map): Constructor> { + public static with (Types: S, jsonMap?: Map): Constructor> { return class extends Struct { constructor (registry: Registry, value?: unknown) { super(registry, Types, value as string, jsonMap);