diff --git a/integration/simple/simple-json-test.ts b/integration/simple/simple-json-test.ts index f7a114591..2a5213bad 100644 --- a/integration/simple/simple-json-test.ts +++ b/integration/simple/simple-json-test.ts @@ -183,9 +183,13 @@ describe("simple json", () => { const s1 = { entitiesById: { "1": { id: "1" } }, intLookup: { 1: 0 }, + boolLookup: { true: 0 }, }; expect(SimpleWithMap.fromJSON(s1)).toMatchInlineSnapshot(` { + "boolLookup": Map { + true => 0, + }, "entitiesById": { "1": { "id": 1, @@ -215,6 +219,7 @@ describe("simple json", () => { const s1 = SimpleWithMap.fromJSON(json); expect(s1).toMatchInlineSnapshot(` { + "boolLookup": Map {}, "entitiesById": {}, "intLookup": {}, "longLookup": {}, @@ -231,7 +236,7 @@ describe("simple json", () => { expect(s1.mapOfTimestamps["b"]).toBeInstanceOf(Date); }); - it("encodes maps of bytes", () => { + it("encodes maps", () => { const s1: SimpleWithMap = { entitiesById: {}, intLookup: {}, @@ -243,10 +248,14 @@ describe("simple json", () => { }, mapOfStringValues: {}, longLookup: {}, + boolLookup: new Map([[true, 1]]), }; const json = SimpleWithMap.toJSON(s1); expect(json).toMatchInlineSnapshot(` { + "boolLookup": { + "true": 1, + }, "mapOfBytes": { "a": "AQI=", "b": "AQID", @@ -265,6 +274,7 @@ describe("simple json", () => { const s1 = SimpleWithMap.fromJSON(json); expect(s1).toMatchInlineSnapshot(` { + "boolLookup": Map {}, "entitiesById": {}, "intLookup": {}, "longLookup": {}, diff --git a/integration/simple/simple-test.ts b/integration/simple/simple-test.ts index ca1bfb1d7..013a1b999 100644 --- a/integration/simple/simple-test.ts +++ b/integration/simple/simple-test.ts @@ -242,11 +242,15 @@ describe("simple", () => { mapOfBytes: {}, mapOfStringValues: { a: "1", b: "", c: undefined }, longLookup: { 1: 2, 2: 1 }, + boolLookup: new Map([[true, 1]]), }; // const s2 = PbSimpleWithMap.toObject(PbSimpleWithMap.decode(SimpleWithMap.encode(s1).finish())); const s2 = SimpleWithMap.decode(SimpleWithMap.encode(s1).finish()); expect(s2).toMatchInlineSnapshot(` { + "boolLookup": Map { + true => 1, + }, "entitiesById": { "1": { "id": 1, @@ -285,6 +289,7 @@ describe("simple", () => { mapOfBytes: {}, mapOfStringValues: { foo: "", bar: undefined }, longLookup: { 1: 2, 2: 1 }, + boolLookup: new Map([[true, 1]]), }; const s2 = SimpleWithMap.decode(SimpleWithMap.encode(s1).finish()); expect(s2).toEqual(s1); @@ -305,6 +310,7 @@ describe("simple", () => { mapOfTimestamps: {}, mapOfBytes: {}, mapOfStringValues: {}, + boolLookup: new Map(), }); }); @@ -345,6 +351,7 @@ describe("simple", () => { }); expect(s1).toMatchInlineSnapshot(` { + "boolLookup": Map {}, "entitiesById": {}, "intLookup": { "1": 2, diff --git a/integration/simple/simple.bin b/integration/simple/simple.bin index 2d67ca34d..ec0b68f26 100644 Binary files a/integration/simple/simple.bin and b/integration/simple/simple.bin differ diff --git a/integration/simple/simple.proto b/integration/simple/simple.proto index ee732a1f8..e65f2b5bb 100644 --- a/integration/simple/simple.proto +++ b/integration/simple/simple.proto @@ -99,6 +99,7 @@ message SimpleWithMap { map mapOfBytes = 5; map mapOfStringValues = 6; map longLookup = 7; + map boolLookup = 8; } message SimpleWithSnakeCaseMap { diff --git a/integration/simple/simple.ts b/integration/simple/simple.ts index f7dd4bb05..023bea748 100644 --- a/integration/simple/simple.ts +++ b/integration/simple/simple.ts @@ -199,6 +199,7 @@ export interface SimpleWithMap { mapOfBytes: { [key: string]: Uint8Array }; mapOfStringValues: { [key: string]: string | undefined }; longLookup: { [key: number]: number }; + boolLookup: Map; } export interface SimpleWithMap_EntitiesByIdEntry { @@ -236,6 +237,11 @@ export interface SimpleWithMap_LongLookupEntry { value: number; } +export interface SimpleWithMap_BoolLookupEntry { + key: boolean; + value: number; +} + export interface SimpleWithSnakeCaseMap { entitiesById: { [key: number]: Entity }; } @@ -1176,6 +1182,7 @@ function createBaseSimpleWithMap(): SimpleWithMap { mapOfBytes: {}, mapOfStringValues: {}, longLookup: {}, + boolLookup: new Map(), }; } @@ -1204,6 +1211,9 @@ export const SimpleWithMap = { Object.entries(message.longLookup).forEach(([key, value]) => { SimpleWithMap_LongLookupEntry.encode({ key: key as any, value }, writer.uint32(58).fork()).ldelim(); }); + (message.boolLookup).forEach((value, key) => { + SimpleWithMap_BoolLookupEntry.encode({ key: key as any, value }, writer.uint32(66).fork()).ldelim(); + }); return writer; }, @@ -1284,6 +1294,16 @@ export const SimpleWithMap = { message.longLookup[entry7.key] = entry7.value; } continue; + case 8: + if (tag !== 66) { + break; + } + + const entry8 = SimpleWithMap_BoolLookupEntry.decode(reader, reader.uint32()); + if (entry8.value !== undefined) { + message.boolLookup.set(entry8.key, entry8.value); + } + continue; } if ((tag & 7) === 4 || tag === 0) { break; @@ -1340,6 +1360,12 @@ export const SimpleWithMap = { return acc; }, {}) : {}, + boolLookup: isObject(object.boolLookup) + ? Object.entries(object.boolLookup).reduce>((acc, [key, value]) => { + acc.set(globalThis.Boolean(key), Number(value)); + return acc; + }, new Map()) + : new Map(), }; }, @@ -1408,6 +1434,12 @@ export const SimpleWithMap = { }); } } + if (message.boolLookup?.size) { + obj.boolLookup = {}; + message.boolLookup.forEach((v, k) => { + obj.boolLookup[globalThis.String(k)] = Math.round(v); + }); + } return obj; }, @@ -1478,6 +1510,15 @@ export const SimpleWithMap = { }, {}, ); + message.boolLookup = (() => { + const m = new Map(); + (object.boolLookup as Map ?? new Map()).forEach((value, key) => { + if (value !== undefined) { + m.set(key, Number(value)); + } + }); + return m; + })(); return message; }, }; @@ -2009,6 +2050,82 @@ export const SimpleWithMap_LongLookupEntry = { }, }; +function createBaseSimpleWithMap_BoolLookupEntry(): SimpleWithMap_BoolLookupEntry { + return { key: false, value: 0 }; +} + +export const SimpleWithMap_BoolLookupEntry = { + encode(message: SimpleWithMap_BoolLookupEntry, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.key === true) { + writer.uint32(8).bool(message.key); + } + if (message.value !== 0) { + writer.uint32(16).int64(message.value); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): SimpleWithMap_BoolLookupEntry { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseSimpleWithMap_BoolLookupEntry(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 8) { + break; + } + + message.key = reader.bool(); + continue; + case 2: + if (tag !== 16) { + break; + } + + message.value = longToNumber(reader.int64() as Long); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): SimpleWithMap_BoolLookupEntry { + return { + key: isSet(object.key) ? Boolean(object.key) : false, + value: isSet(object.value) ? Number(object.value) : 0, + }; + }, + + toJSON(message: SimpleWithMap_BoolLookupEntry): unknown { + const obj: any = {}; + if (message.key === true) { + obj.key = message.key; + } + if (message.value !== 0) { + obj.value = Math.round(message.value); + } + return obj; + }, + + create, I>>(base?: I): SimpleWithMap_BoolLookupEntry { + return SimpleWithMap_BoolLookupEntry.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>( + object: I, + ): SimpleWithMap_BoolLookupEntry { + const message = createBaseSimpleWithMap_BoolLookupEntry(); + message.key = object.key ?? false; + message.value = object.value ?? 0; + return message; + }, +}; + function createBaseSimpleWithSnakeCaseMap(): SimpleWithSnakeCaseMap { return { entitiesById: {} }; } diff --git a/src/main.ts b/src/main.ts index 375c7e68b..86209fb3f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -14,14 +14,12 @@ import { detectMapType, getEnumMethod, isAnyValueType, - isAnyValueTypeName, isBytes, isBytesValueType, isEnum, isFieldMaskType, isFieldMaskTypeName, isListValueType, - isListValueTypeName, isLong, isLongValueType, isMapType, @@ -32,7 +30,6 @@ import { isRepeated, isScalar, isStructType, - isStructTypeName, isTimestamp, isValueType, isWholeNumber, @@ -40,21 +37,21 @@ import { isWithinOneOfThatShouldBeUnion, notDefaultCheck, packedType, + shouldGenerateJSMapType, toReaderCall, toTypeName, - shouldGenerateJSMapType, valueTypeName, } from "./types"; import SourceInfo, { Fields } from "./sourceInfo"; import { assertInstanceOf, - getFieldJsonName, FormattedMethodDescriptor, + getFieldJsonName, + getPropertyAccessor, + impFile, impProto, maybeAddComment, maybePrefixPackage, - getPropertyAccessor, - impFile, } from "./utils"; import { camelToSnake, capitalize, maybeSnakeToCamel } from "./case"; import { @@ -75,7 +72,7 @@ import { generateGrpcMethodDesc, generateGrpcServiceDesc, } from "./generate-grpc-web"; -import { generateEncodeTransform, generateDecodeTransform } from "./generate-async-iterable"; +import { generateDecodeTransform, generateEncodeTransform } from "./generate-async-iterable"; import { generateEnum } from "./enums"; import { visit, visitServices } from "./visit"; import { addTypeToMessages, DateOption, EnvOption, LongOption, OneofOption, Options, ServiceOption } from "./options"; @@ -2287,6 +2284,8 @@ function convertFromObjectKey( } else { return code`${ctx.utils.globalThis}.Number(${variableName})`; } + } else if (keyField.type === FieldDescriptorProto_Type.TYPE_BOOL) { + return code`${ctx.utils.globalThis}.Boolean(${variableName})`; } else { return code`${ctx.utils.globalThis}.Number(${variableName})`; } @@ -2309,6 +2308,8 @@ function convertToObjectKey( } else { return code`${variableName}`; } + } else if (keyField.type === FieldDescriptorProto_Type.TYPE_BOOL) { + return code`${ctx.utils.globalThis}.String(${variableName})`; } else { return code`${variableName}`; } diff --git a/src/types.ts b/src/types.ts index d3722e75e..64213c358 100644 --- a/src/types.ts +++ b/src/types.ts @@ -680,7 +680,10 @@ export function shouldGenerateJSMapType(ctx: Context, message: DescriptorProto, if (!mapType) { return false; } - return isLong(mapType.keyField) && ctx.options.forceLong === LongOption.LONG; + return ( + mapType.keyField.type === FieldDescriptorProto_Type.TYPE_BOOL || + (isLong(mapType.keyField) && ctx.options.forceLong === LongOption.LONG) + ); } export function detectMapType(