diff --git a/integration/const-enum/const-enum.bin b/integration/const-enum/const-enum.bin index 12ff3f8df..978b99505 100644 Binary files a/integration/const-enum/const-enum.bin and b/integration/const-enum/const-enum.bin differ diff --git a/integration/const-enum/const-enum.proto b/integration/const-enum/const-enum.proto index 064d3829e..ea48dbf46 100644 --- a/integration/const-enum/const-enum.proto +++ b/integration/const-enum/const-enum.proto @@ -9,4 +9,5 @@ message DividerData { } DividerType type = 1; + map typeMap = 2; } diff --git a/integration/const-enum/const-enum.ts b/integration/const-enum/const-enum.ts index df1bdc295..ccf8b0375 100644 --- a/integration/const-enum/const-enum.ts +++ b/integration/const-enum/const-enum.ts @@ -6,6 +6,7 @@ export const protobufPackage = ''; export interface DividerData { type: DividerData_DividerType; + typeMap: { [key: string]: DividerData_DividerType }; } export const enum DividerData_DividerType { @@ -67,8 +68,13 @@ export function dividerData_DividerTypeToNumber(object: DividerData_DividerType) } } +export interface DividerData_TypeMapEntry { + key: string; + value: DividerData_DividerType; +} + function createBaseDividerData(): DividerData { - return { type: DividerData_DividerType.DOUBLE }; + return { type: DividerData_DividerType.DOUBLE, typeMap: {} }; } export const DividerData = { @@ -76,6 +82,9 @@ export const DividerData = { if (message.type !== DividerData_DividerType.DOUBLE) { writer.uint32(8).int32(dividerData_DividerTypeToNumber(message.type)); } + Object.entries(message.typeMap).forEach(([key, value]) => { + DividerData_TypeMapEntry.encode({ key: key as any, value }, writer.uint32(18).fork()).ldelim(); + }); return writer; }, @@ -89,6 +98,12 @@ export const DividerData = { case 1: message.type = dividerData_DividerTypeFromJSON(reader.int32()); break; + case 2: + const entry2 = DividerData_TypeMapEntry.decode(reader, reader.uint32()); + if (entry2.value !== undefined) { + message.typeMap[entry2.key] = entry2.value; + } + break; default: reader.skipType(tag & 7); break; @@ -100,18 +115,97 @@ export const DividerData = { fromJSON(object: any): DividerData { return { type: isSet(object.type) ? dividerData_DividerTypeFromJSON(object.type) : DividerData_DividerType.DOUBLE, + typeMap: isObject(object.typeMap) + ? Object.entries(object.typeMap).reduce<{ [key: string]: DividerData_DividerType }>((acc, [key, value]) => { + acc[key] = value as DividerData_DividerType; + return acc; + }, {}) + : {}, }; }, toJSON(message: DividerData): unknown { const obj: any = {}; message.type !== undefined && (obj.type = dividerData_DividerTypeToJSON(message.type)); + obj.typeMap = {}; + if (message.typeMap) { + Object.entries(message.typeMap).forEach(([k, v]) => { + obj.typeMap[k] = dividerData_DividerTypeToJSON(v); + }); + } return obj; }, fromPartial, I>>(object: I): DividerData { const message = createBaseDividerData(); message.type = object.type ?? DividerData_DividerType.DOUBLE; + message.typeMap = Object.entries(object.typeMap ?? {}).reduce<{ [key: string]: DividerData_DividerType }>( + (acc, [key, value]) => { + if (value !== undefined) { + acc[key] = value as DividerData_DividerType; + } + return acc; + }, + {} + ); + return message; + }, +}; + +function createBaseDividerData_TypeMapEntry(): DividerData_TypeMapEntry { + return { key: '', value: DividerData_DividerType.DOUBLE }; +} + +export const DividerData_TypeMapEntry = { + encode(message: DividerData_TypeMapEntry, writer: Writer = Writer.create()): Writer { + if (message.key !== '') { + writer.uint32(10).string(message.key); + } + if (message.value !== DividerData_DividerType.DOUBLE) { + writer.uint32(16).int32(dividerData_DividerTypeToNumber(message.value)); + } + return writer; + }, + + decode(input: Reader | Uint8Array, length?: number): DividerData_TypeMapEntry { + const reader = input instanceof Reader ? input : new Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseDividerData_TypeMapEntry(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.key = reader.string(); + break; + case 2: + message.value = dividerData_DividerTypeFromJSON(reader.int32()); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): DividerData_TypeMapEntry { + return { + key: isSet(object.key) ? String(object.key) : '', + value: isSet(object.value) ? dividerData_DividerTypeFromJSON(object.value) : DividerData_DividerType.DOUBLE, + }; + }, + + toJSON(message: DividerData_TypeMapEntry): unknown { + const obj: any = {}; + message.key !== undefined && (obj.key = message.key); + message.value !== undefined && (obj.value = dividerData_DividerTypeToJSON(message.value)); + return obj; + }, + + fromPartial, I>>(object: I): DividerData_TypeMapEntry { + const message = createBaseDividerData_TypeMapEntry(); + message.key = object.key ?? ''; + message.value = object.value ?? DividerData_DividerType.DOUBLE; return message; }, }; @@ -140,6 +234,10 @@ if (util.Long !== Long) { configure(); } +function isObject(value: any): boolean { + return typeof value === 'object' && value !== null; +} + function isSet(value: any): boolean { return value !== null && value !== undefined; } diff --git a/integration/grpc-js/google/protobuf/struct.ts b/integration/grpc-js/google/protobuf/struct.ts index 6a3fc5696..714868a3a 100644 --- a/integration/grpc-js/google/protobuf/struct.ts +++ b/integration/grpc-js/google/protobuf/struct.ts @@ -60,8 +60,8 @@ export interface Struct_FieldsEntry { /** * `Value` represents a dynamically typed value which can be either * null, a number, a string, a boolean, a recursive struct value, or a - * list of values. A producer of value is expected to set one of these - * variants. Absence of any variant indicates an error. + * list of values. A producer of value is expected to set one of that + * variants, absence of any variant indicates an error. * * The JSON representation for `Value` is JSON value. */ diff --git a/integration/grpc-js/google/protobuf/wrappers.bin b/integration/grpc-js/google/protobuf/wrappers.bin index 7f34d6ce0..5b4b18672 100644 Binary files a/integration/grpc-js/google/protobuf/wrappers.bin and b/integration/grpc-js/google/protobuf/wrappers.bin differ diff --git a/integration/grpc-js/simple.bin b/integration/grpc-js/simple.bin index a4adec606..97028a38b 100644 Binary files a/integration/grpc-js/simple.bin and b/integration/grpc-js/simple.bin differ diff --git a/integration/oneof-unions/google/protobuf/struct.ts b/integration/oneof-unions/google/protobuf/struct.ts index f524ec268..6c956232d 100644 --- a/integration/oneof-unions/google/protobuf/struct.ts +++ b/integration/oneof-unions/google/protobuf/struct.ts @@ -60,8 +60,8 @@ export interface Struct_FieldsEntry { /** * `Value` represents a dynamically typed value which can be either * null, a number, a string, a boolean, a recursive struct value, or a - * list of values. A producer of value is expected to set one of these - * variants. Absence of any variant indicates an error. + * list of values. A producer of value is expected to set one of that + * variants, absence of any variant indicates an error. * * The JSON representation for `Value` is JSON value. */ diff --git a/integration/oneof-unions/oneof.bin b/integration/oneof-unions/oneof.bin index 999540787..3fc0ea08e 100644 Binary files a/integration/oneof-unions/oneof.bin and b/integration/oneof-unions/oneof.bin differ diff --git a/integration/simple-prototype-defaults/simple.ts b/integration/simple-prototype-defaults/simple.ts index f0e68fb5a..4d595aba9 100644 --- a/integration/simple-prototype-defaults/simple.ts +++ b/integration/simple-prototype-defaults/simple.ts @@ -1812,7 +1812,7 @@ export const SimpleWithMapOfEnums = { return { enumsById: isObject(object.enumsById) ? Object.entries(object.enumsById).reduce<{ [key: number]: StateEnum }>((acc, [key, value]) => { - acc[Number(key)] = value as number; + acc[Number(key)] = value as StateEnum; return acc; }, {}) : {}, @@ -1835,7 +1835,7 @@ export const SimpleWithMapOfEnums = { message.enumsById = Object.entries(object.enumsById ?? {}).reduce<{ [key: number]: StateEnum }>( (acc, [key, value]) => { if (value !== undefined) { - acc[Number(key)] = value as number; + acc[Number(key)] = value as StateEnum; } return acc; }, diff --git a/integration/simple-string-enums/google/protobuf/struct.ts b/integration/simple-string-enums/google/protobuf/struct.ts index 47a6f2b7b..2bef7a5de 100644 --- a/integration/simple-string-enums/google/protobuf/struct.ts +++ b/integration/simple-string-enums/google/protobuf/struct.ts @@ -69,8 +69,8 @@ export interface Struct_FieldsEntry { /** * `Value` represents a dynamically typed value which can be either * null, a number, a string, a boolean, a recursive struct value, or a - * list of values. A producer of value is expected to set one of these - * variants. Absence of any variant indicates an error. + * list of values. A producer of value is expected to set one of that + * variants, absence of any variant indicates an error. * * The JSON representation for `Value` is JSON value. */ diff --git a/integration/simple-string-enums/simple-test.ts b/integration/simple-string-enums/simple-test.ts index 978f49317..8613ca949 100644 --- a/integration/simple-string-enums/simple-test.ts +++ b/integration/simple-string-enums/simple-test.ts @@ -1,9 +1,15 @@ import { Simple, StateEnum } from './simple'; -import { NullValue } from "./google/protobuf/struct"; +import { NullValue } from './google/protobuf/struct'; describe('simple-string-enums', () => { it('encodes', () => { - const s1: Simple = { name: 'a', state: StateEnum.ON, states: [StateEnum.ON, StateEnum.OFF], nullValue: NullValue.NULL_VALUE }; + const s1: Simple = { + name: 'a', + state: StateEnum.ON, + states: [StateEnum.ON, StateEnum.OFF], + nullValue: NullValue.NULL_VALUE, + stateMap: { on: StateEnum.ON }, + }; const b = Simple.encode(s1).finish(); const s2 = Simple.decode(b); expect(s2).toMatchInlineSnapshot(` @@ -11,6 +17,9 @@ describe('simple-string-enums', () => { "name": "a", "nullValue": "NULL_VALUE", "state": "ON", + "stateMap": Object { + "on": "ON", + }, "states": Array [ "ON", "OFF", diff --git a/integration/simple-string-enums/simple.bin b/integration/simple-string-enums/simple.bin index d19b05e80..605ccd9f5 100644 Binary files a/integration/simple-string-enums/simple.bin and b/integration/simple-string-enums/simple.bin differ diff --git a/integration/simple-string-enums/simple.proto b/integration/simple-string-enums/simple.proto index ccf132a83..a5159b974 100644 --- a/integration/simple-string-enums/simple.proto +++ b/integration/simple-string-enums/simple.proto @@ -7,6 +7,7 @@ message Simple { StateEnum state = 4; repeated StateEnum states = 5; google.protobuf.NullValue nullValue = 6; + map stateMap = 7; } enum StateEnum { diff --git a/integration/simple-string-enums/simple.ts b/integration/simple-string-enums/simple.ts index 5dfbbfe2c..0db778b91 100644 --- a/integration/simple-string-enums/simple.ts +++ b/integration/simple-string-enums/simple.ts @@ -61,10 +61,16 @@ export interface Simple { state: StateEnum; states: StateEnum[]; nullValue: NullValue; + stateMap: { [key: string]: StateEnum }; +} + +export interface Simple_StateMapEntry { + key: string; + value: StateEnum; } function createBaseSimple(): Simple { - return { name: '', state: StateEnum.UNKNOWN, states: [], nullValue: NullValue.NULL_VALUE }; + return { name: '', state: StateEnum.UNKNOWN, states: [], nullValue: NullValue.NULL_VALUE, stateMap: {} }; } export const Simple = { @@ -83,6 +89,9 @@ export const Simple = { if (message.nullValue !== NullValue.NULL_VALUE) { writer.uint32(48).int32(nullValueToNumber(message.nullValue)); } + Object.entries(message.stateMap).forEach(([key, value]) => { + Simple_StateMapEntry.encode({ key: key as any, value }, writer.uint32(58).fork()).ldelim(); + }); return writer; }, @@ -112,6 +121,12 @@ export const Simple = { case 6: message.nullValue = nullValueFromJSON(reader.int32()); break; + case 7: + const entry7 = Simple_StateMapEntry.decode(reader, reader.uint32()); + if (entry7.value !== undefined) { + message.stateMap[entry7.key] = entry7.value; + } + break; default: reader.skipType(tag & 7); break; @@ -126,6 +141,12 @@ export const Simple = { state: isSet(object.state) ? stateEnumFromJSON(object.state) : StateEnum.UNKNOWN, states: Array.isArray(object?.states) ? object.states.map((e: any) => stateEnumFromJSON(e)) : [], nullValue: isSet(object.nullValue) ? nullValueFromJSON(object.nullValue) : NullValue.NULL_VALUE, + stateMap: isObject(object.stateMap) + ? Object.entries(object.stateMap).reduce<{ [key: string]: StateEnum }>((acc, [key, value]) => { + acc[key] = value as StateEnum; + return acc; + }, {}) + : {}, }; }, @@ -139,6 +160,12 @@ export const Simple = { obj.states = []; } message.nullValue !== undefined && (obj.nullValue = nullValueToJSON(message.nullValue)); + obj.stateMap = {}; + if (message.stateMap) { + Object.entries(message.stateMap).forEach(([k, v]) => { + obj.stateMap[k] = stateEnumToJSON(v); + }); + } return obj; }, @@ -148,6 +175,73 @@ export const Simple = { message.state = object.state ?? StateEnum.UNKNOWN; message.states = object.states?.map((e) => e) || []; message.nullValue = object.nullValue ?? NullValue.NULL_VALUE; + message.stateMap = Object.entries(object.stateMap ?? {}).reduce<{ [key: string]: StateEnum }>( + (acc, [key, value]) => { + if (value !== undefined) { + acc[key] = value as StateEnum; + } + return acc; + }, + {} + ); + return message; + }, +}; + +function createBaseSimple_StateMapEntry(): Simple_StateMapEntry { + return { key: '', value: StateEnum.UNKNOWN }; +} + +export const Simple_StateMapEntry = { + encode(message: Simple_StateMapEntry, writer: Writer = Writer.create()): Writer { + if (message.key !== '') { + writer.uint32(10).string(message.key); + } + if (message.value !== StateEnum.UNKNOWN) { + writer.uint32(16).int32(stateEnumToNumber(message.value)); + } + return writer; + }, + + decode(input: Reader | Uint8Array, length?: number): Simple_StateMapEntry { + const reader = input instanceof Reader ? input : new Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseSimple_StateMapEntry(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.key = reader.string(); + break; + case 2: + message.value = stateEnumFromJSON(reader.int32()); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): Simple_StateMapEntry { + return { + key: isSet(object.key) ? String(object.key) : '', + value: isSet(object.value) ? stateEnumFromJSON(object.value) : StateEnum.UNKNOWN, + }; + }, + + toJSON(message: Simple_StateMapEntry): unknown { + const obj: any = {}; + message.key !== undefined && (obj.key = message.key); + message.value !== undefined && (obj.value = stateEnumToJSON(message.value)); + return obj; + }, + + fromPartial, I>>(object: I): Simple_StateMapEntry { + const message = createBaseSimple_StateMapEntry(); + message.key = object.key ?? ''; + message.value = object.value ?? StateEnum.UNKNOWN; return message; }, }; @@ -176,6 +270,10 @@ if (util.Long !== Long) { configure(); } +function isObject(value: any): boolean { + return typeof value === 'object' && value !== null; +} + function isSet(value: any): boolean { return value !== null && value !== undefined; } diff --git a/integration/simple/simple.ts b/integration/simple/simple.ts index 500fe92ce..9dc11c5e2 100644 --- a/integration/simple/simple.ts +++ b/integration/simple/simple.ts @@ -1804,7 +1804,7 @@ export const SimpleWithMapOfEnums = { return { enumsById: isObject(object.enumsById) ? Object.entries(object.enumsById).reduce<{ [key: number]: StateEnum }>((acc, [key, value]) => { - acc[Number(key)] = value as number; + acc[Number(key)] = value as StateEnum; return acc; }, {}) : {}, @@ -1827,7 +1827,7 @@ export const SimpleWithMapOfEnums = { message.enumsById = Object.entries(object.enumsById ?? {}).reduce<{ [key: number]: StateEnum }>( (acc, [key, value]) => { if (value !== undefined) { - acc[Number(key)] = value as number; + acc[Number(key)] = value as StateEnum; } return acc; }, diff --git a/integration/struct/google/protobuf/struct.ts b/integration/struct/google/protobuf/struct.ts index 6a3fc5696..714868a3a 100644 --- a/integration/struct/google/protobuf/struct.ts +++ b/integration/struct/google/protobuf/struct.ts @@ -60,8 +60,8 @@ export interface Struct_FieldsEntry { /** * `Value` represents a dynamically typed value which can be either * null, a number, a string, a boolean, a recursive struct value, or a - * list of values. A producer of value is expected to set one of these - * variants. Absence of any variant indicates an error. + * list of values. A producer of value is expected to set one of that + * variants, absence of any variant indicates an error. * * The JSON representation for `Value` is JSON value. */ diff --git a/integration/struct/struct.bin b/integration/struct/struct.bin index d9a1b260e..af5a5b0a3 100644 Binary files a/integration/struct/struct.bin and b/integration/struct/struct.bin differ diff --git a/integration/type-registry/bar/bar.bin b/integration/type-registry/bar/bar.bin index 3aa6c75d1..e3a191a27 100644 Binary files a/integration/type-registry/bar/bar.bin and b/integration/type-registry/bar/bar.bin differ diff --git a/integration/type-registry/foo.bin b/integration/type-registry/foo.bin index 85b788882..8b76ef718 100644 Binary files a/integration/type-registry/foo.bin and b/integration/type-registry/foo.bin differ diff --git a/integration/type-registry/google/protobuf/struct.ts b/integration/type-registry/google/protobuf/struct.ts index a3d18eb06..d297c0b8a 100644 --- a/integration/type-registry/google/protobuf/struct.ts +++ b/integration/type-registry/google/protobuf/struct.ts @@ -63,8 +63,8 @@ export interface Struct_FieldsEntry { /** * `Value` represents a dynamically typed value which can be either * null, a number, a string, a boolean, a recursive struct value, or a - * list of values. A producer of value is expected to set one of these - * variants. Absence of any variant indicates an error. + * list of values. A producer of value is expected to set one of that + * variants, absence of any variant indicates an error. * * The JSON representation for `Value` is JSON value. */ diff --git a/integration/value/google/protobuf/struct.ts b/integration/value/google/protobuf/struct.ts index 6a3fc5696..714868a3a 100644 --- a/integration/value/google/protobuf/struct.ts +++ b/integration/value/google/protobuf/struct.ts @@ -60,8 +60,8 @@ export interface Struct_FieldsEntry { /** * `Value` represents a dynamically typed value which can be either * null, a number, a string, a boolean, a recursive struct value, or a - * list of values. A producer of value is expected to set one of these - * variants. Absence of any variant indicates an error. + * list of values. A producer of value is expected to set one of that + * variants, absence of any variant indicates an error. * * The JSON representation for `Value` is JSON value. */ diff --git a/integration/value/value.bin b/integration/value/value.bin index 3564a9fe8..1c552a212 100644 Binary files a/integration/value/value.bin and b/integration/value/value.bin differ diff --git a/src/main.ts b/src/main.ts index d7319af64..9d1abdf63 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1186,40 +1186,38 @@ function generateFromJson(ctx: Context, fullName: string, fullTypeName: string, } } else if (isMessage(field)) { if (isRepeated(field) && isMapType(ctx, messageDesc, field)) { - const valueType = (typeMap.get(field.typeName)![2] as DescriptorProto).field[1]; - if (isPrimitive(valueType)) { + const { valueField, valueType } = detectMapType(ctx, messageDesc, field)!; + if (isPrimitive(valueField)) { // TODO Can we not copy/paste this from ^? - if (isBytes(valueType)) { + if (isBytes(valueField)) { if (options.env === EnvOption.NODE) { return code`Buffer.from(${utils.bytesFromBase64}(${from} as string))`; } else { return code`${utils.bytesFromBase64}(${from} as string)`; } - } else if (isLong(valueType) && options.forceLong === LongOption.LONG) { + } else if (isLong(valueField) && options.forceLong === LongOption.LONG) { return code`Long.fromValue(${from} as Long | string)`; - } else if (isEnum(valueType)) { - return code`${from} as number`; + } else if (isEnum(valueField)) { + return code`${from} as ${valueType}`; } else { - const cstr = capitalize(basicTypeName(ctx, valueType).toCodeString()); + const cstr = capitalize(valueType.toCodeString()); return code`${cstr}(${from})`; } - } else if (isObjectId(valueType) && options.useMongoObjectId) { + } else if (isObjectId(valueField) && options.useMongoObjectId) { return code`${utils.fromJsonObjectId}(${from})`; - } else if (isTimestamp(valueType) && options.useDate === DateOption.STRING) { + } else if (isTimestamp(valueField) && options.useDate === DateOption.STRING) { return code`String(${from})`; } else if ( - isTimestamp(valueType) && + isTimestamp(valueField) && (options.useDate === DateOption.DATE || options.useDate === DateOption.TIMESTAMP) ) { return code`${utils.fromJsonTimestamp}(${from})`; - } else if (isValueType(ctx, valueType)) { - const type = basicTypeName(ctx, valueType); - return code`${from} as ${type}`; - } else if (isAnyValueType(valueType)) { + } else if (isValueType(ctx, valueField)) { + return code`${from} as ${valueType}`; + } else if (isAnyValueType(valueField)) { return code`${from}`; } else { - const type = basicTypeName(ctx, valueType); - return code`${type}.fromJSON(${from})`; + return code`${valueType}.fromJSON(${from})`; } } else { const type = basicTypeName(ctx, field); @@ -1484,31 +1482,31 @@ function generateFromPartial(ctx: Context, fullName: string, messageDesc: Descri return code`${from}`; } else if (isMessage(field)) { if (isRepeated(field) && isMapType(ctx, messageDesc, field)) { - const valueType = (typeMap.get(field.typeName)![2] as DescriptorProto).field[1]; - if (isPrimitive(valueType)) { - if (isBytes(valueType)) { + const { valueField, valueType } = detectMapType(ctx, messageDesc, field)!; + if (isPrimitive(valueField)) { + if (isBytes(valueField)) { return code`${from}`; - } else if (isEnum(valueType)) { - return code`${from} as number`; - } else if (isLong(valueType) && options.forceLong === LongOption.LONG) { + } else if (isEnum(valueField)) { + return code`${from} as ${valueType}`; + } else if (isLong(valueField) && options.forceLong === LongOption.LONG) { return code`Long.fromValue(${from})`; } else { - const cstr = capitalize(basicTypeName(ctx, valueType).toCodeString()); + const cstr = capitalize(valueType.toCodeString()); return code`${cstr}(${from})`; } - } else if (isAnyValueType(valueType)) { + } else if (isAnyValueType(valueField)) { return code`${from}`; - } else if (isObjectId(valueType) && options.useMongoObjectId) { + } else if (isObjectId(valueField) && options.useMongoObjectId) { return code`${from} as mongodb.ObjectId`; } else if ( - isTimestamp(valueType) && + isTimestamp(valueField) && (options.useDate === DateOption.DATE || options.useDate === DateOption.STRING) ) { return code`${from}`; - } else if (isValueType(ctx, valueType)) { + } else if (isValueType(ctx, valueField)) { return code`${from}`; } else { - const type = basicTypeName(ctx, valueType); + const type = basicTypeName(ctx, valueField); return code`${type}.fromPartial(${from})`; } } else if (isAnyValueType(field)) { diff --git a/src/types.ts b/src/types.ts index 6f96527ea..00d00ccad 100644 --- a/src/types.ts +++ b/src/types.ts @@ -623,7 +623,15 @@ export function detectMapType( ctx: Context, messageDesc: DescriptorProto, fieldDesc: FieldDescriptorProto -): { messageDesc: DescriptorProto; keyType: Code; valueType: Code } | undefined { +): + | { + messageDesc: DescriptorProto; + keyField: FieldDescriptorProto; + keyType: Code; + valueField: FieldDescriptorProto; + valueType: Code; + } + | undefined { const { typeMap } = ctx; if ( fieldDesc.label === FieldDescriptorProto_Label.LABEL_REPEATED && @@ -631,10 +639,11 @@ export function detectMapType( ) { const mapType = typeMap.get(fieldDesc.typeName)![2] as DescriptorProto; if (!mapType.options?.mapEntry) return undefined; - const keyType = toTypeName(ctx, messageDesc, mapType.field[0]); + const [keyField, valueField] = mapType.field; + const keyType = toTypeName(ctx, messageDesc, keyField); // use basicTypeName because we don't need the '| undefined' - const valueType = basicTypeName(ctx, mapType.field[1]); - return { messageDesc: mapType, keyType, valueType }; + const valueType = basicTypeName(ctx, valueField); + return { messageDesc: mapType, keyField, keyType, valueField, valueType }; } return undefined; }