diff --git a/README.markdown b/README.markdown index a872f5304..2690327cd 100644 --- a/README.markdown +++ b/README.markdown @@ -517,6 +517,8 @@ Generated code will be placed in the Gradle build directory. This will disable `exportCommonSymbols` to avoid name collisions on the common symbols. +- With `--emitDefaultValues=json-methods`, the generated toJSON method will emit scalars like `0` and `""` as json fields. + ### NestJS Support We have a great way of working together with [nestjs](https://docs.nestjs.com/microservices/grpc). `ts-proto` generates `interfaces` and `decorators` for you controller, client. For more information see the [nestjs readme](NESTJS.markdown). diff --git a/integration/emit-default-values-json/emit-default-values-test.ts b/integration/emit-default-values-json/emit-default-values-test.ts new file mode 100644 index 000000000..e5e9de5e3 --- /dev/null +++ b/integration/emit-default-values-json/emit-default-values-test.ts @@ -0,0 +1,16 @@ +import { DefaultValuesTest } from './test' + +describe("emit-default-values-json", () => { + it("encodes to json correctly", () => { + const output = DefaultValuesTest.toJSON(DefaultValuesTest.fromPartial({})) + + expect(output).toStrictEqual({ + data: "", + description: "", + id: 0, + long: 0, + state: "UNKNOWN", + truth: false + }) + }) +}); diff --git a/integration/emit-default-values-json/google/protobuf/timestamp.ts b/integration/emit-default-values-json/google/protobuf/timestamp.ts new file mode 100644 index 000000000..ad9c1e7d1 --- /dev/null +++ b/integration/emit-default-values-json/google/protobuf/timestamp.ts @@ -0,0 +1,232 @@ +/* eslint-disable */ +import * as _m0 from "protobufjs/minimal"; +import Long = require("long"); + +export const protobufPackage = "google.protobuf"; + +/** + * A Timestamp represents a point in time independent of any time zone or local + * calendar, encoded as a count of seconds and fractions of seconds at + * nanosecond resolution. The count is relative to an epoch at UTC midnight on + * January 1, 1970, in the proleptic Gregorian calendar which extends the + * Gregorian calendar backwards to year one. + * + * All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap + * second table is needed for interpretation, using a [24-hour linear + * smear](https://developers.google.com/time/smear). + * + * The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By + * restricting to that range, we ensure that we can convert to and from [RFC + * 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. + * + * # Examples + * + * Example 1: Compute Timestamp from POSIX `time()`. + * + * Timestamp timestamp; + * timestamp.set_seconds(time(NULL)); + * timestamp.set_nanos(0); + * + * Example 2: Compute Timestamp from POSIX `gettimeofday()`. + * + * struct timeval tv; + * gettimeofday(&tv, NULL); + * + * Timestamp timestamp; + * timestamp.set_seconds(tv.tv_sec); + * timestamp.set_nanos(tv.tv_usec * 1000); + * + * Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. + * + * FILETIME ft; + * GetSystemTimeAsFileTime(&ft); + * UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; + * + * // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z + * // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. + * Timestamp timestamp; + * timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); + * timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); + * + * Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. + * + * long millis = System.currentTimeMillis(); + * + * Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) + * .setNanos((int) ((millis % 1000) * 1000000)).build(); + * + * Example 5: Compute Timestamp from Java `Instant.now()`. + * + * Instant now = Instant.now(); + * + * Timestamp timestamp = + * Timestamp.newBuilder().setSeconds(now.getEpochSecond()) + * .setNanos(now.getNano()).build(); + * + * Example 6: Compute Timestamp from current time in Python. + * + * timestamp = Timestamp() + * timestamp.GetCurrentTime() + * + * # JSON Mapping + * + * In JSON format, the Timestamp type is encoded as a string in the + * [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the + * format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" + * where {year} is always expressed using four digits while {month}, {day}, + * {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional + * seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), + * are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone + * is required. A proto3 JSON serializer should always use UTC (as indicated by + * "Z") when printing the Timestamp type and a proto3 JSON parser should be + * able to accept both UTC and other timezones (as indicated by an offset). + * + * For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past + * 01:30 UTC on January 15, 2017. + * + * In JavaScript, one can convert a Date object to this format using the + * standard + * [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) + * method. In Python, a standard `datetime.datetime` object can be converted + * to this format using + * [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with + * the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use + * the Joda Time's [`ISODateTimeFormat.dateTime()`]( + * http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D + * ) to obtain a formatter capable of generating timestamps in this format. + */ +export interface Timestamp { + /** + * Represents seconds of UTC time since Unix epoch + * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to + * 9999-12-31T23:59:59Z inclusive. + */ + seconds: number; + /** + * Non-negative fractions of a second at nanosecond resolution. Negative + * second values with fractions must still have non-negative nanos values + * that count forward in time. Must be from 0 to 999,999,999 + * inclusive. + */ + nanos: number; +} + +function createBaseTimestamp(): Timestamp { + return { seconds: 0, nanos: 0 }; +} + +export const Timestamp = { + encode(message: Timestamp, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.seconds !== 0) { + writer.uint32(8).int64(message.seconds); + } + if (message.nanos !== 0) { + writer.uint32(16).int32(message.nanos); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): Timestamp { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseTimestamp(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 8) { + break; + } + + message.seconds = longToNumber(reader.int64() as Long); + continue; + case 2: + if (tag !== 16) { + break; + } + + message.nanos = reader.int32(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): Timestamp { + return { + seconds: isSet(object.seconds) ? Number(object.seconds) : 0, + nanos: isSet(object.nanos) ? Number(object.nanos) : 0, + }; + }, + + toJSON(message: Timestamp): unknown { + const obj: any = {}; + if (message.seconds !== undefined) { + obj.seconds = Math.round(message.seconds); + } + if (message.nanos !== undefined) { + obj.nanos = Math.round(message.nanos); + } + return obj; + }, + + create, I>>(base?: I): Timestamp { + return Timestamp.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): Timestamp { + const message = createBaseTimestamp(); + message.seconds = object.seconds ?? 0; + message.nanos = object.nanos ?? 0; + return message; + }, +}; + +declare const self: any | undefined; +declare const window: any | undefined; +declare const global: any | undefined; +const tsProtoGlobalThis: any = (() => { + if (typeof globalThis !== "undefined") { + return globalThis; + } + if (typeof self !== "undefined") { + return self; + } + if (typeof window !== "undefined") { + return window; + } + if (typeof global !== "undefined") { + return global; + } + throw "Unable to locate global object"; +})(); + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin ? T + : T extends Array ? Array> : T extends ReadonlyArray ? ReadonlyArray> + : T extends {} ? { [K in keyof T]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin ? P + : P & { [K in keyof P]: Exact } & { [K in Exclude>]: never }; + +function longToNumber(long: Long): number { + if (long.gt(Number.MAX_SAFE_INTEGER)) { + throw new tsProtoGlobalThis.Error("Value is larger than Number.MAX_SAFE_INTEGER"); + } + return long.toNumber(); +} + +if (_m0.util.Long !== Long) { + _m0.util.Long = Long as any; + _m0.configure(); +} + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} diff --git a/integration/emit-default-values-json/parameters.txt b/integration/emit-default-values-json/parameters.txt new file mode 100644 index 000000000..f4853ac36 --- /dev/null +++ b/integration/emit-default-values-json/parameters.txt @@ -0,0 +1 @@ +emitDefaultValues=json-methods diff --git a/integration/emit-default-values-json/test.bin b/integration/emit-default-values-json/test.bin new file mode 100644 index 000000000..de2b1d54b Binary files /dev/null and b/integration/emit-default-values-json/test.bin differ diff --git a/integration/emit-default-values-json/test.proto b/integration/emit-default-values-json/test.proto new file mode 100644 index 000000000..d85509f37 --- /dev/null +++ b/integration/emit-default-values-json/test.proto @@ -0,0 +1,42 @@ +syntax = "proto3"; +package defaultvalues; + +import "google/protobuf/timestamp.proto"; + +message DefaultValuesTest { + int32 id = 1; + Child child = 2; + StateEnum state = 3; + int64 long = 4; + bool truth = 5; + string description = 6; + bytes data = 7; + + repeated int32 rep_id = 11; + repeated Child rep_child = 12; + repeated StateEnum rep_state = 13; + repeated int64 rep_long = 14; + repeated bool rep_truth = 15; + repeated string rep_description = 16; + repeated bytes rep_data = 17; + + optional int32 opt_id = 21; + optional Child opt_child = 22; + optional StateEnum opt_state = 23; + optional int64 opt_long = 24; + optional bool opt_truth = 25; + optional string opt_description = 26; + optional bytes opt_data = 27; + + map translations = 30; + + google.protobuf.Timestamp timestamp = 31; +} + +enum StateEnum { + UNKNOWN = 0; + ON = 2; + OFF = 3; +} + +message Child {} diff --git a/integration/emit-default-values-json/test.ts b/integration/emit-default-values-json/test.ts new file mode 100644 index 000000000..a4d193e0c --- /dev/null +++ b/integration/emit-default-values-json/test.ts @@ -0,0 +1,779 @@ +/* eslint-disable */ +import * as _m0 from "protobufjs/minimal"; +import { Timestamp } from "./google/protobuf/timestamp"; +import Long = require("long"); + +export const protobufPackage = "defaultvalues"; + +export enum StateEnum { + UNKNOWN = 0, + ON = 2, + OFF = 3, + UNRECOGNIZED = -1, +} + +export function stateEnumFromJSON(object: any): StateEnum { + switch (object) { + case 0: + case "UNKNOWN": + return StateEnum.UNKNOWN; + case 2: + case "ON": + return StateEnum.ON; + case 3: + case "OFF": + return StateEnum.OFF; + case -1: + case "UNRECOGNIZED": + default: + return StateEnum.UNRECOGNIZED; + } +} + +export function stateEnumToJSON(object: StateEnum): string { + switch (object) { + case StateEnum.UNKNOWN: + return "UNKNOWN"; + case StateEnum.ON: + return "ON"; + case StateEnum.OFF: + return "OFF"; + case StateEnum.UNRECOGNIZED: + default: + return "UNRECOGNIZED"; + } +} + +export interface DefaultValuesTest { + id: number; + child: Child | undefined; + state: StateEnum; + long: number; + truth: boolean; + description: string; + data: Uint8Array; + repId: number[]; + repChild: Child[]; + repState: StateEnum[]; + repLong: number[]; + repTruth: boolean[]; + repDescription: string[]; + repData: Uint8Array[]; + optId?: number | undefined; + optChild?: Child | undefined; + optState?: StateEnum | undefined; + optLong?: number | undefined; + optTruth?: boolean | undefined; + optDescription?: string | undefined; + optData?: Uint8Array | undefined; + translations: { [key: string]: string }; + timestamp: Date | undefined; +} + +export interface DefaultValuesTest_TranslationsEntry { + key: string; + value: string; +} + +export interface Child { +} + +function createBaseDefaultValuesTest(): DefaultValuesTest { + return { + id: 0, + child: undefined, + state: 0, + long: 0, + truth: false, + description: "", + data: new Uint8Array(0), + repId: [], + repChild: [], + repState: [], + repLong: [], + repTruth: [], + repDescription: [], + repData: [], + optId: undefined, + optChild: undefined, + optState: undefined, + optLong: undefined, + optTruth: undefined, + optDescription: undefined, + optData: undefined, + translations: {}, + timestamp: undefined, + }; +} + +export const DefaultValuesTest = { + encode(message: DefaultValuesTest, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.id !== 0) { + writer.uint32(8).int32(message.id); + } + if (message.child !== undefined) { + Child.encode(message.child, writer.uint32(18).fork()).ldelim(); + } + if (message.state !== 0) { + writer.uint32(24).int32(message.state); + } + if (message.long !== 0) { + writer.uint32(32).int64(message.long); + } + if (message.truth === true) { + writer.uint32(40).bool(message.truth); + } + if (message.description !== "") { + writer.uint32(50).string(message.description); + } + if (message.data.length !== 0) { + writer.uint32(58).bytes(message.data); + } + writer.uint32(90).fork(); + for (const v of message.repId) { + writer.int32(v); + } + writer.ldelim(); + for (const v of message.repChild) { + Child.encode(v!, writer.uint32(98).fork()).ldelim(); + } + writer.uint32(106).fork(); + for (const v of message.repState) { + writer.int32(v); + } + writer.ldelim(); + writer.uint32(114).fork(); + for (const v of message.repLong) { + writer.int64(v); + } + writer.ldelim(); + writer.uint32(122).fork(); + for (const v of message.repTruth) { + writer.bool(v); + } + writer.ldelim(); + for (const v of message.repDescription) { + writer.uint32(130).string(v!); + } + for (const v of message.repData) { + writer.uint32(138).bytes(v!); + } + if (message.optId !== undefined) { + writer.uint32(168).int32(message.optId); + } + if (message.optChild !== undefined) { + Child.encode(message.optChild, writer.uint32(178).fork()).ldelim(); + } + if (message.optState !== undefined) { + writer.uint32(184).int32(message.optState); + } + if (message.optLong !== undefined) { + writer.uint32(192).int64(message.optLong); + } + if (message.optTruth !== undefined) { + writer.uint32(200).bool(message.optTruth); + } + if (message.optDescription !== undefined) { + writer.uint32(210).string(message.optDescription); + } + if (message.optData !== undefined) { + writer.uint32(218).bytes(message.optData); + } + Object.entries(message.translations).forEach(([key, value]) => { + DefaultValuesTest_TranslationsEntry.encode({ key: key as any, value }, writer.uint32(242).fork()).ldelim(); + }); + if (message.timestamp !== undefined) { + Timestamp.encode(toTimestamp(message.timestamp), writer.uint32(250).fork()).ldelim(); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): DefaultValuesTest { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseDefaultValuesTest(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 8) { + break; + } + + message.id = reader.int32(); + continue; + case 2: + if (tag !== 18) { + break; + } + + message.child = Child.decode(reader, reader.uint32()); + continue; + case 3: + if (tag !== 24) { + break; + } + + message.state = reader.int32() as any; + continue; + case 4: + if (tag !== 32) { + break; + } + + message.long = longToNumber(reader.int64() as Long); + continue; + case 5: + if (tag !== 40) { + break; + } + + message.truth = reader.bool(); + continue; + case 6: + if (tag !== 50) { + break; + } + + message.description = reader.string(); + continue; + case 7: + if (tag !== 58) { + break; + } + + message.data = reader.bytes(); + continue; + case 11: + if (tag === 88) { + message.repId.push(reader.int32()); + + continue; + } + + if (tag === 90) { + const end2 = reader.uint32() + reader.pos; + while (reader.pos < end2) { + message.repId.push(reader.int32()); + } + + continue; + } + + break; + case 12: + if (tag !== 98) { + break; + } + + message.repChild.push(Child.decode(reader, reader.uint32())); + continue; + case 13: + if (tag === 104) { + message.repState.push(reader.int32() as any); + + continue; + } + + if (tag === 106) { + const end2 = reader.uint32() + reader.pos; + while (reader.pos < end2) { + message.repState.push(reader.int32() as any); + } + + continue; + } + + break; + case 14: + if (tag === 112) { + message.repLong.push(longToNumber(reader.int64() as Long)); + + continue; + } + + if (tag === 114) { + const end2 = reader.uint32() + reader.pos; + while (reader.pos < end2) { + message.repLong.push(longToNumber(reader.int64() as Long)); + } + + continue; + } + + break; + case 15: + if (tag === 120) { + message.repTruth.push(reader.bool()); + + continue; + } + + if (tag === 122) { + const end2 = reader.uint32() + reader.pos; + while (reader.pos < end2) { + message.repTruth.push(reader.bool()); + } + + continue; + } + + break; + case 16: + if (tag !== 130) { + break; + } + + message.repDescription.push(reader.string()); + continue; + case 17: + if (tag !== 138) { + break; + } + + message.repData.push(reader.bytes()); + continue; + case 21: + if (tag !== 168) { + break; + } + + message.optId = reader.int32(); + continue; + case 22: + if (tag !== 178) { + break; + } + + message.optChild = Child.decode(reader, reader.uint32()); + continue; + case 23: + if (tag !== 184) { + break; + } + + message.optState = reader.int32() as any; + continue; + case 24: + if (tag !== 192) { + break; + } + + message.optLong = longToNumber(reader.int64() as Long); + continue; + case 25: + if (tag !== 200) { + break; + } + + message.optTruth = reader.bool(); + continue; + case 26: + if (tag !== 210) { + break; + } + + message.optDescription = reader.string(); + continue; + case 27: + if (tag !== 218) { + break; + } + + message.optData = reader.bytes(); + continue; + case 30: + if (tag !== 242) { + break; + } + + const entry30 = DefaultValuesTest_TranslationsEntry.decode(reader, reader.uint32()); + if (entry30.value !== undefined) { + message.translations[entry30.key] = entry30.value; + } + continue; + case 31: + if (tag !== 250) { + break; + } + + message.timestamp = fromTimestamp(Timestamp.decode(reader, reader.uint32())); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): DefaultValuesTest { + return { + id: isSet(object.id) ? Number(object.id) : 0, + child: isSet(object.child) ? Child.fromJSON(object.child) : undefined, + state: isSet(object.state) ? stateEnumFromJSON(object.state) : 0, + long: isSet(object.long) ? Number(object.long) : 0, + truth: isSet(object.truth) ? Boolean(object.truth) : false, + description: isSet(object.description) ? String(object.description) : "", + data: isSet(object.data) ? bytesFromBase64(object.data) : new Uint8Array(0), + repId: Array.isArray(object?.repId) ? object.repId.map((e: any) => Number(e)) : [], + repChild: Array.isArray(object?.repChild) ? object.repChild.map((e: any) => Child.fromJSON(e)) : [], + repState: Array.isArray(object?.repState) ? object.repState.map((e: any) => stateEnumFromJSON(e)) : [], + repLong: Array.isArray(object?.repLong) ? object.repLong.map((e: any) => Number(e)) : [], + repTruth: Array.isArray(object?.repTruth) ? object.repTruth.map((e: any) => Boolean(e)) : [], + repDescription: Array.isArray(object?.repDescription) ? object.repDescription.map((e: any) => String(e)) : [], + repData: Array.isArray(object?.repData) ? object.repData.map((e: any) => bytesFromBase64(e)) : [], + optId: isSet(object.optId) ? Number(object.optId) : undefined, + optChild: isSet(object.optChild) ? Child.fromJSON(object.optChild) : undefined, + optState: isSet(object.optState) ? stateEnumFromJSON(object.optState) : undefined, + optLong: isSet(object.optLong) ? Number(object.optLong) : undefined, + optTruth: isSet(object.optTruth) ? Boolean(object.optTruth) : undefined, + optDescription: isSet(object.optDescription) ? String(object.optDescription) : undefined, + optData: isSet(object.optData) ? bytesFromBase64(object.optData) : undefined, + translations: isObject(object.translations) + ? Object.entries(object.translations).reduce<{ [key: string]: string }>((acc, [key, value]) => { + acc[key] = String(value); + return acc; + }, {}) + : {}, + timestamp: isSet(object.timestamp) ? fromJsonTimestamp(object.timestamp) : undefined, + }; + }, + + toJSON(message: DefaultValuesTest): unknown { + const obj: any = {}; + if (message.id !== undefined) { + obj.id = Math.round(message.id); + } + if (message.child !== undefined) { + obj.child = Child.toJSON(message.child); + } + if (message.state !== undefined) { + obj.state = stateEnumToJSON(message.state); + } + if (message.long !== undefined) { + obj.long = Math.round(message.long); + } + if (message.truth !== undefined) { + obj.truth = message.truth; + } + if (message.description !== undefined) { + obj.description = message.description; + } + if (message.data !== undefined) { + obj.data = base64FromBytes(message.data); + } + if (message.repId?.length) { + obj.repId = message.repId.map((e) => Math.round(e)); + } + if (message.repChild?.length) { + obj.repChild = message.repChild.map((e) => Child.toJSON(e)); + } + if (message.repState?.length) { + obj.repState = message.repState.map((e) => stateEnumToJSON(e)); + } + if (message.repLong?.length) { + obj.repLong = message.repLong.map((e) => Math.round(e)); + } + if (message.repTruth?.length) { + obj.repTruth = message.repTruth; + } + if (message.repDescription?.length) { + obj.repDescription = message.repDescription; + } + if (message.repData?.length) { + obj.repData = message.repData.map((e) => base64FromBytes(e)); + } + if (message.optId !== undefined) { + obj.optId = Math.round(message.optId); + } + if (message.optChild !== undefined) { + obj.optChild = Child.toJSON(message.optChild); + } + if (message.optState !== undefined) { + obj.optState = stateEnumToJSON(message.optState); + } + if (message.optLong !== undefined) { + obj.optLong = Math.round(message.optLong); + } + if (message.optTruth !== undefined) { + obj.optTruth = message.optTruth; + } + if (message.optDescription !== undefined) { + obj.optDescription = message.optDescription; + } + if (message.optData !== undefined) { + obj.optData = base64FromBytes(message.optData); + } + if (message.translations) { + const entries = Object.entries(message.translations); + if (entries.length > 0) { + obj.translations = {}; + entries.forEach(([k, v]) => { + obj.translations[k] = v; + }); + } + } + if (message.timestamp !== undefined) { + obj.timestamp = message.timestamp.toISOString(); + } + return obj; + }, + + create, I>>(base?: I): DefaultValuesTest { + return DefaultValuesTest.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): DefaultValuesTest { + const message = createBaseDefaultValuesTest(); + message.id = object.id ?? 0; + message.child = (object.child !== undefined && object.child !== null) ? Child.fromPartial(object.child) : undefined; + message.state = object.state ?? 0; + message.long = object.long ?? 0; + message.truth = object.truth ?? false; + message.description = object.description ?? ""; + message.data = object.data ?? new Uint8Array(0); + message.repId = object.repId?.map((e) => e) || []; + message.repChild = object.repChild?.map((e) => Child.fromPartial(e)) || []; + message.repState = object.repState?.map((e) => e) || []; + message.repLong = object.repLong?.map((e) => e) || []; + message.repTruth = object.repTruth?.map((e) => e) || []; + message.repDescription = object.repDescription?.map((e) => e) || []; + message.repData = object.repData?.map((e) => e) || []; + message.optId = object.optId ?? undefined; + message.optChild = (object.optChild !== undefined && object.optChild !== null) + ? Child.fromPartial(object.optChild) + : undefined; + message.optState = object.optState ?? undefined; + message.optLong = object.optLong ?? undefined; + message.optTruth = object.optTruth ?? undefined; + message.optDescription = object.optDescription ?? undefined; + message.optData = object.optData ?? undefined; + message.translations = Object.entries(object.translations ?? {}).reduce<{ [key: string]: string }>( + (acc, [key, value]) => { + if (value !== undefined) { + acc[key] = String(value); + } + return acc; + }, + {}, + ); + message.timestamp = object.timestamp ?? undefined; + return message; + }, +}; + +function createBaseDefaultValuesTest_TranslationsEntry(): DefaultValuesTest_TranslationsEntry { + return { key: "", value: "" }; +} + +export const DefaultValuesTest_TranslationsEntry = { + encode(message: DefaultValuesTest_TranslationsEntry, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.key !== "") { + writer.uint32(10).string(message.key); + } + if (message.value !== "") { + writer.uint32(18).string(message.value); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): DefaultValuesTest_TranslationsEntry { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseDefaultValuesTest_TranslationsEntry(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.key = reader.string(); + continue; + case 2: + if (tag !== 18) { + break; + } + + message.value = reader.string(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): DefaultValuesTest_TranslationsEntry { + return { key: isSet(object.key) ? String(object.key) : "", value: isSet(object.value) ? String(object.value) : "" }; + }, + + toJSON(message: DefaultValuesTest_TranslationsEntry): unknown { + const obj: any = {}; + if (message.key !== undefined) { + obj.key = message.key; + } + if (message.value !== undefined) { + obj.value = message.value; + } + return obj; + }, + + create, I>>( + base?: I, + ): DefaultValuesTest_TranslationsEntry { + return DefaultValuesTest_TranslationsEntry.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>( + object: I, + ): DefaultValuesTest_TranslationsEntry { + const message = createBaseDefaultValuesTest_TranslationsEntry(); + message.key = object.key ?? ""; + message.value = object.value ?? ""; + return message; + }, +}; + +function createBaseChild(): Child { + return {}; +} + +export const Child = { + encode(_: Child, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): Child { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseChild(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(_: any): Child { + return {}; + }, + + toJSON(_: Child): unknown { + const obj: any = {}; + return obj; + }, + + create, I>>(base?: I): Child { + return Child.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(_: I): Child { + const message = createBaseChild(); + return message; + }, +}; + +declare const self: any | undefined; +declare const window: any | undefined; +declare const global: any | undefined; +const tsProtoGlobalThis: any = (() => { + if (typeof globalThis !== "undefined") { + return globalThis; + } + if (typeof self !== "undefined") { + return self; + } + if (typeof window !== "undefined") { + return window; + } + if (typeof global !== "undefined") { + return global; + } + throw "Unable to locate global object"; +})(); + +function bytesFromBase64(b64: string): Uint8Array { + if (tsProtoGlobalThis.Buffer) { + return Uint8Array.from(tsProtoGlobalThis.Buffer.from(b64, "base64")); + } else { + const bin = tsProtoGlobalThis.atob(b64); + const arr = new Uint8Array(bin.length); + for (let i = 0; i < bin.length; ++i) { + arr[i] = bin.charCodeAt(i); + } + return arr; + } +} + +function base64FromBytes(arr: Uint8Array): string { + if (tsProtoGlobalThis.Buffer) { + return tsProtoGlobalThis.Buffer.from(arr).toString("base64"); + } else { + const bin: string[] = []; + arr.forEach((byte) => { + bin.push(String.fromCharCode(byte)); + }); + return tsProtoGlobalThis.btoa(bin.join("")); + } +} + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin ? T + : T extends Array ? Array> : T extends ReadonlyArray ? ReadonlyArray> + : T extends {} ? { [K in keyof T]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin ? P + : P & { [K in keyof P]: Exact } & { [K in Exclude>]: never }; + +function toTimestamp(date: Date): Timestamp { + const seconds = date.getTime() / 1_000; + const nanos = (date.getTime() % 1_000) * 1_000_000; + return { seconds, nanos }; +} + +function fromTimestamp(t: Timestamp): Date { + let millis = (t.seconds || 0) * 1_000; + millis += (t.nanos || 0) / 1_000_000; + return new Date(millis); +} + +function fromJsonTimestamp(o: any): Date { + if (o instanceof Date) { + return o; + } else if (typeof o === "string") { + return new Date(o); + } else { + return fromTimestamp(Timestamp.fromJSON(o)); + } +} + +function longToNumber(long: Long): number { + if (long.gt(Number.MAX_SAFE_INTEGER)) { + throw new tsProtoGlobalThis.Error("Value is larger than Number.MAX_SAFE_INTEGER"); + } + return long.toNumber(); +} + +if (_m0.util.Long !== Long) { + _m0.util.Long = Long as any; + _m0.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/src/main.ts b/src/main.ts index 23fe63000..2a1cc6b0d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2071,8 +2071,9 @@ function generateToJson( } `); } else { + let emitDefaultValuesForJson = ctx.options.emitDefaultValues.includes("json-methods"); const check = - (isScalar(field) || isEnum(field)) && !isWithinOneOf(field) + (isScalar(field) || isEnum(field)) && !(isWithinOneOf(field) || emitDefaultValuesForJson) ? notDefaultCheck(ctx, field, messageDesc.options, `message.${fieldName}`) : `message.${fieldName} !== undefined`; diff --git a/src/options.ts b/src/options.ts index dcf515ad9..04c959f65 100644 --- a/src/options.ts +++ b/src/options.ts @@ -39,6 +39,7 @@ export type Options = { snakeToCamel: Array<"json" | "keys">; forceLong: LongOption; useOptionals: boolean | "none" | "messages" | "all"; // boolean is deprecated + emitDefaultValues: Array<"json-methods">; useDate: DateOption; useMongoObjectId: boolean; oneof: OneofOption; @@ -88,6 +89,7 @@ export function defaultOptions(): Options { return { context: false, snakeToCamel: ["json", "keys"], + emitDefaultValues: [], forceLong: LongOption.NUMBER, useOptionals: "none", useDate: DateOption.DATE, @@ -205,6 +207,12 @@ export function optionsFromParameter(parameter: string | undefined): Options { options.snakeToCamel = (options.snakeToCamel as string).split("_") as any; } + if ((options.emitDefaultValues as any) === "json-methods") { + options.emitDefaultValues = ["json-methods"]; + } else { + options.emitDefaultValues = []; + } + if (options.useJsonWireFormat) { if (!options.onlyTypes) { // useJsonWireFormat requires onlyTypes=true diff --git a/tests/options-test.ts b/tests/options-test.ts index 0124707e2..4d2ce183d 100644 --- a/tests/options-test.ts +++ b/tests/options-test.ts @@ -9,6 +9,7 @@ describe("options", () => { "addNestjsRestParameter": false, "constEnums": false, "context": false, + "emitDefaultValues": [], "emitImportedFiles": true, "enumsAsLiterals": false, "env": "both",