From 9813d630cc6c4e8f4772400d7975c2ff458dfb2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Matheson=20Wergeland?= Date: Mon, 30 May 2022 16:15:53 +0200 Subject: [PATCH 1/3] New option 'useJsonWireFormat'. Fixes issue #571 --- README.markdown | 5 +++++ src/enums.ts | 9 +++++---- src/options.ts | 2 ++ src/types.ts | 11 +++++++++-- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/README.markdown b/README.markdown index 7a313c23a..fed17ae2f 100644 --- a/README.markdown +++ b/README.markdown @@ -406,6 +406,11 @@ Generated code will be placed in the Gradle build directory. Note that, as indicated, this means Object.keys will not include set-by-default fields, so if you have code that iterates over messages keys in a generic fashion, it will have to also iterate over keys inherited from the prototype. +- With `--ts_proto_opt=useJsonWireFormat=true`, the generated code will reflect the JSON representation of Protobuf messages. + + Requires `onlyTypes=true`. Implies `useDate=string` and `stringEnums=true`. This option is to generate types that can be directly used with marshalling/unmarshalling Protobuf messages serialized as JSON. + You may also want to set `useOptionals=all`, as gRPC gateways are not required to send default value for scalar values. + ### Only Types If you're looking for `ts-proto` to generate only types for your Protobuf types then passing all three of `outputEncodeMethods`, `outputJsonMethods`, and `outputClientImpl` as `false` is probably what you want, i.e.: diff --git a/src/enums.ts b/src/enums.ts index a1ad7620a..18c6b8cad 100644 --- a/src/enums.ts +++ b/src/enums.ts @@ -27,19 +27,20 @@ export function generateEnum( } const delimiter = options.enumsAsLiterals ? ':' : '='; + const stringEnums = options.stringEnums || (options.onlyTypes && options.useJsonWireFormat); enumDesc.value.forEach((valueDesc, index) => { const info = sourceInfo.lookup(Fields.enum.value, index); maybeAddComment(info, chunks, valueDesc.options?.deprecated, `${valueDesc.name} - `); chunks.push( - code`${valueDesc.name} ${delimiter} ${options.stringEnums ? `"${valueDesc.name}"` : valueDesc.number.toString()},` + code`${valueDesc.name} ${delimiter} ${stringEnums ? `"${valueDesc.name}"` : valueDesc.number.toString()},` ); }); if (options.unrecognizedEnum) chunks.push(code` ${UNRECOGNIZED_ENUM_NAME} ${delimiter} ${ - options.stringEnums ? `"${UNRECOGNIZED_ENUM_NAME}"` : UNRECOGNIZED_ENUM_VALUE.toString() + stringEnums ? `"${UNRECOGNIZED_ENUM_NAME}"` : UNRECOGNIZED_ENUM_VALUE.toString() },`); if (options.enumsAsLiterals) { @@ -50,7 +51,7 @@ export function generateEnum( chunks.push(code`}`); } - if (options.outputJsonMethods || (options.stringEnums && options.outputEncodeMethods)) { + if (options.outputJsonMethods || (stringEnums && options.outputEncodeMethods)) { chunks.push(code`\n`); chunks.push(generateEnumFromJson(ctx, fullName, enumDesc)); } @@ -58,7 +59,7 @@ export function generateEnum( chunks.push(code`\n`); chunks.push(generateEnumToJson(ctx, fullName, enumDesc)); } - if (options.stringEnums && options.outputEncodeMethods) { + if (stringEnums && options.outputEncodeMethods) { chunks.push(code`\n`); chunks.push(generateEnumToNumber(ctx, fullName, enumDesc)); } diff --git a/src/options.ts b/src/options.ts index 4bdf8ff2d..1a774b8bf 100644 --- a/src/options.ts +++ b/src/options.ts @@ -64,6 +64,7 @@ export type Options = { useExactTypes: boolean; unknownFields: boolean; usePrototypeForDefaults: boolean; + useJsonWireFormat: boolean }; export function defaultOptions(): Options { @@ -101,6 +102,7 @@ export function defaultOptions(): Options { useExactTypes: true, unknownFields: false, usePrototypeForDefaults: false, + useJsonWireFormat: false, }; } diff --git a/src/types.ts b/src/types.ts index 53c888fa0..e12e8ef51 100644 --- a/src/types.ts +++ b/src/types.ts @@ -462,6 +462,7 @@ export function isEmptyType(typeName: string): boolean { } export function valueTypeName(ctx: Context, typeName: string): Code | undefined { + const useJsonWireFormat = ctx.options.onlyTypes && ctx.options.useJsonWireFormat switch (typeName) { case '.google.protobuf.StringValue': return code`string`; @@ -477,7 +478,9 @@ export function valueTypeName(ctx: Context, typeName: string): Code | undefined case '.google.protobuf.BoolValue': return code`boolean`; case '.google.protobuf.BytesValue': - return ctx.options.env === EnvOption.NODE ? code`Buffer` : code`Uint8Array`; + return ctx.options.env === EnvOption.NODE + ? code`Buffer` + : useJsonWireFormat ? code`string` : code`Uint8Array`; case '.google.protobuf.ListValue': return code`Array`; case '.google.protobuf.Value': @@ -485,7 +488,11 @@ export function valueTypeName(ctx: Context, typeName: string): Code | undefined case '.google.protobuf.Struct': return code`{[key: string]: any}`; case '.google.protobuf.FieldMask': - return code`string[]`; + return useJsonWireFormat ? code`string` : code`string[]`; + case '.google.protobuf.Duration': + return useJsonWireFormat ? code`string` : undefined; + case '.google.protobuf.Timestamp': + return useJsonWireFormat ? code`string` : undefined; default: return undefined; } From fde788bb6de9b3a1656e3c69a6f3e768b974ec8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Matheson=20Wergeland?= Date: Tue, 31 May 2022 12:39:14 +0200 Subject: [PATCH 2/3] Added test. --- .../google/protobuf/duration.ts | 80 +++++++ .../google/protobuf/field_mask.ts | 208 ++++++++++++++++++ .../google/protobuf/timestamp.ts | 111 ++++++++++ .../use-json-wire-format/parameters.txt | 1 + .../use-json-wire-format-test.ts | 15 ++ .../use-json-wire-format.bin | Bin 0 -> 19667 bytes .../use-json-wire-format.proto | 11 + .../use-json-wire-format.ts | 9 + tests/options-test.ts | 1 + 9 files changed, 436 insertions(+) create mode 100644 integration/use-json-wire-format/google/protobuf/duration.ts create mode 100644 integration/use-json-wire-format/google/protobuf/field_mask.ts create mode 100644 integration/use-json-wire-format/google/protobuf/timestamp.ts create mode 100644 integration/use-json-wire-format/parameters.txt create mode 100644 integration/use-json-wire-format/use-json-wire-format-test.ts create mode 100644 integration/use-json-wire-format/use-json-wire-format.bin create mode 100644 integration/use-json-wire-format/use-json-wire-format.proto create mode 100644 integration/use-json-wire-format/use-json-wire-format.ts diff --git a/integration/use-json-wire-format/google/protobuf/duration.ts b/integration/use-json-wire-format/google/protobuf/duration.ts new file mode 100644 index 000000000..003dbf46e --- /dev/null +++ b/integration/use-json-wire-format/google/protobuf/duration.ts @@ -0,0 +1,80 @@ +/* eslint-disable */ +export const protobufPackage = 'google.protobuf'; + +/** + * A Duration represents a signed, fixed-length span of time represented + * as a count of seconds and fractions of seconds at nanosecond + * resolution. It is independent of any calendar and concepts like "day" + * or "month". It is related to Timestamp in that the difference between + * two Timestamp values is a Duration and it can be added or subtracted + * from a Timestamp. Range is approximately +-10,000 years. + * + * # Examples + * + * Example 1: Compute Duration from two Timestamps in pseudo code. + * + * Timestamp start = ...; + * Timestamp end = ...; + * Duration duration = ...; + * + * duration.seconds = end.seconds - start.seconds; + * duration.nanos = end.nanos - start.nanos; + * + * if (duration.seconds < 0 && duration.nanos > 0) { + * duration.seconds += 1; + * duration.nanos -= 1000000000; + * } else if (duration.seconds > 0 && duration.nanos < 0) { + * duration.seconds -= 1; + * duration.nanos += 1000000000; + * } + * + * Example 2: Compute Timestamp from Timestamp + Duration in pseudo code. + * + * Timestamp start = ...; + * Duration duration = ...; + * Timestamp end = ...; + * + * end.seconds = start.seconds + duration.seconds; + * end.nanos = start.nanos + duration.nanos; + * + * if (end.nanos < 0) { + * end.seconds -= 1; + * end.nanos += 1000000000; + * } else if (end.nanos >= 1000000000) { + * end.seconds += 1; + * end.nanos -= 1000000000; + * } + * + * Example 3: Compute Duration from datetime.timedelta in Python. + * + * td = datetime.timedelta(days=3, minutes=10) + * duration = Duration() + * duration.FromTimedelta(td) + * + * # JSON Mapping + * + * In JSON format, the Duration type is encoded as a string rather than an + * object, where the string ends in the suffix "s" (indicating seconds) and + * is preceded by the number of seconds, with nanoseconds expressed as + * fractional seconds. For example, 3 seconds with 0 nanoseconds should be + * encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should + * be expressed in JSON format as "3.000000001s", and 3 seconds and 1 + * microsecond should be expressed in JSON format as "3.000001s". + */ +export interface Duration { + /** + * Signed seconds of the span of time. Must be from -315,576,000,000 + * to +315,576,000,000 inclusive. Note: these bounds are computed from: + * 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years + */ + seconds?: number; + /** + * Signed fractions of a second at nanosecond resolution of the span + * of time. Durations less than one second are represented with a 0 + * `seconds` field and a positive or negative `nanos` field. For durations + * of one second or more, a non-zero value for the `nanos` field must be + * of the same sign as the `seconds` field. Must be from -999,999,999 + * to +999,999,999 inclusive. + */ + nanos?: number; +} diff --git a/integration/use-json-wire-format/google/protobuf/field_mask.ts b/integration/use-json-wire-format/google/protobuf/field_mask.ts new file mode 100644 index 000000000..f33e4ce2e --- /dev/null +++ b/integration/use-json-wire-format/google/protobuf/field_mask.ts @@ -0,0 +1,208 @@ +/* eslint-disable */ +export const protobufPackage = 'google.protobuf'; + +/** + * `FieldMask` represents a set of symbolic field paths, for example: + * + * paths: "f.a" + * paths: "f.b.d" + * + * Here `f` represents a field in some root message, `a` and `b` + * fields in the message found in `f`, and `d` a field found in the + * message in `f.b`. + * + * Field masks are used to specify a subset of fields that should be + * returned by a get operation or modified by an update operation. + * Field masks also have a custom JSON encoding (see below). + * + * # Field Masks in Projections + * + * When used in the context of a projection, a response message or + * sub-message is filtered by the API to only contain those fields as + * specified in the mask. For example, if the mask in the previous + * example is applied to a response message as follows: + * + * f { + * a : 22 + * b { + * d : 1 + * x : 2 + * } + * y : 13 + * } + * z: 8 + * + * The result will not contain specific values for fields x,y and z + * (their value will be set to the default, and omitted in proto text + * output): + * + * + * f { + * a : 22 + * b { + * d : 1 + * } + * } + * + * A repeated field is not allowed except at the last position of a + * paths string. + * + * If a FieldMask object is not present in a get operation, the + * operation applies to all fields (as if a FieldMask of all fields + * had been specified). + * + * Note that a field mask does not necessarily apply to the + * top-level response message. In case of a REST get operation, the + * field mask applies directly to the response, but in case of a REST + * list operation, the mask instead applies to each individual message + * in the returned resource list. In case of a REST custom method, + * other definitions may be used. Where the mask applies will be + * clearly documented together with its declaration in the API. In + * any case, the effect on the returned resource/resources is required + * behavior for APIs. + * + * # Field Masks in Update Operations + * + * A field mask in update operations specifies which fields of the + * targeted resource are going to be updated. The API is required + * to only change the values of the fields as specified in the mask + * and leave the others untouched. If a resource is passed in to + * describe the updated values, the API ignores the values of all + * fields not covered by the mask. + * + * If a repeated field is specified for an update operation, new values will + * be appended to the existing repeated field in the target resource. Note that + * a repeated field is only allowed in the last position of a `paths` string. + * + * If a sub-message is specified in the last position of the field mask for an + * update operation, then new value will be merged into the existing sub-message + * in the target resource. + * + * For example, given the target message: + * + * f { + * b { + * d: 1 + * x: 2 + * } + * c: [1] + * } + * + * And an update message: + * + * f { + * b { + * d: 10 + * } + * c: [2] + * } + * + * then if the field mask is: + * + * paths: ["f.b", "f.c"] + * + * then the result will be: + * + * f { + * b { + * d: 10 + * x: 2 + * } + * c: [1, 2] + * } + * + * An implementation may provide options to override this default behavior for + * repeated and message fields. + * + * In order to reset a field's value to the default, the field must + * be in the mask and set to the default value in the provided resource. + * Hence, in order to reset all fields of a resource, provide a default + * instance of the resource and set all fields in the mask, or do + * not provide a mask as described below. + * + * If a field mask is not present on update, the operation applies to + * all fields (as if a field mask of all fields has been specified). + * Note that in the presence of schema evolution, this may mean that + * fields the client does not know and has therefore not filled into + * the request will be reset to their default. If this is unwanted + * behavior, a specific service may require a client to always specify + * a field mask, producing an error if not. + * + * As with get operations, the location of the resource which + * describes the updated values in the request message depends on the + * operation kind. In any case, the effect of the field mask is + * required to be honored by the API. + * + * ## Considerations for HTTP REST + * + * The HTTP kind of an update operation which uses a field mask must + * be set to PATCH instead of PUT in order to satisfy HTTP semantics + * (PUT must only be used for full updates). + * + * # JSON Encoding of Field Masks + * + * In JSON, a field mask is encoded as a single string where paths are + * separated by a comma. Fields name in each path are converted + * to/from lower-camel naming conventions. + * + * As an example, consider the following message declarations: + * + * message Profile { + * User user = 1; + * Photo photo = 2; + * } + * message User { + * string display_name = 1; + * string address = 2; + * } + * + * In proto a field mask for `Profile` may look as such: + * + * mask { + * paths: "user.display_name" + * paths: "photo" + * } + * + * In JSON, the same mask is represented as below: + * + * { + * mask: "user.displayName,photo" + * } + * + * # Field Masks and Oneof Fields + * + * Field masks treat fields in oneofs just as regular fields. Consider the + * following message: + * + * message SampleMessage { + * oneof test_oneof { + * string name = 4; + * SubMessage sub_message = 9; + * } + * } + * + * The field mask can be: + * + * mask { + * paths: "name" + * } + * + * Or: + * + * mask { + * paths: "sub_message" + * } + * + * Note that oneof type names ("test_oneof" in this case) cannot be used in + * paths. + * + * ## Field Mask Verification + * + * The implementation of any API method which has a FieldMask type field in the + * request should verify the included field paths, and return an + * `INVALID_ARGUMENT` error if any path is unmappable. + */ +export interface FieldMask { + /** The set of field mask paths. */ + paths?: string[]; +} diff --git a/integration/use-json-wire-format/google/protobuf/timestamp.ts b/integration/use-json-wire-format/google/protobuf/timestamp.ts new file mode 100644 index 000000000..fe2ef4bac --- /dev/null +++ b/integration/use-json-wire-format/google/protobuf/timestamp.ts @@ -0,0 +1,111 @@ +/* eslint-disable */ +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; +} diff --git a/integration/use-json-wire-format/parameters.txt b/integration/use-json-wire-format/parameters.txt new file mode 100644 index 000000000..70dac47e9 --- /dev/null +++ b/integration/use-json-wire-format/parameters.txt @@ -0,0 +1 @@ +useOptionals=all,outputEncodeMethods=false,outputJsonMethods=false,outputClientImpl=false,onlyTypes=true,useJsonWireFormat=true diff --git a/integration/use-json-wire-format/use-json-wire-format-test.ts b/integration/use-json-wire-format/use-json-wire-format-test.ts new file mode 100644 index 000000000..09fe42923 --- /dev/null +++ b/integration/use-json-wire-format/use-json-wire-format-test.ts @@ -0,0 +1,15 @@ +import { Todo } from './use-json-wire-format'; + +const jan1 = new Date('1970-01-01T00:00:00.000Z'); +const feb1 = new Date('1970-02-01T00:00:00.000Z'); + +describe('useJsonWireFormat=true', () => { + it('generates a type that compiles', () => { + let t: Todo = { + id: "1", + timestamp: "1970-01-01T00:00:00.000Z", + duration: "60s", + updateMask: "id,timestamp,duration" + } + }); +}); diff --git a/integration/use-json-wire-format/use-json-wire-format.bin b/integration/use-json-wire-format/use-json-wire-format.bin new file mode 100644 index 0000000000000000000000000000000000000000..89e572e44d83f4626c430453316aee69c72030f4 GIT binary patch literal 19667 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 literal 0 HcmV?d00001 diff --git a/integration/use-json-wire-format/use-json-wire-format.proto b/integration/use-json-wire-format/use-json-wire-format.proto new file mode 100644 index 000000000..2c025aaef --- /dev/null +++ b/integration/use-json-wire-format/use-json-wire-format.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; +import "google/protobuf/field_mask.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; + +message Todo { + string id = 1; + google.protobuf.Timestamp timestamp = 2; + google.protobuf.Duration duration = 3; + google.protobuf.FieldMask update_mask =4; +} diff --git a/integration/use-json-wire-format/use-json-wire-format.ts b/integration/use-json-wire-format/use-json-wire-format.ts new file mode 100644 index 000000000..64f67634f --- /dev/null +++ b/integration/use-json-wire-format/use-json-wire-format.ts @@ -0,0 +1,9 @@ +/* eslint-disable */ +export const protobufPackage = ''; + +export interface Todo { + id?: string; + timestamp?: string; + duration?: string; + updateMask?: string; +} diff --git a/tests/options-test.ts b/tests/options-test.ts index daf292d7a..8067bddbc 100644 --- a/tests/options-test.ts +++ b/tests/options-test.ts @@ -39,6 +39,7 @@ describe('options', () => { "unrecognizedEnum": true, "useDate": "timestamp", "useExactTypes": true, + "useJsonWireFormat": false, "useMongoObjectId": false, "useOptionals": "none", "usePrototypeForDefaults": false, From cd65bb2dbdd4e640e7c0bbf03717ecaa16873260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Matheson=20Wergeland?= Date: Tue, 31 May 2022 12:42:36 +0200 Subject: [PATCH 3/3] Formatting. --- .../use-json-wire-format/use-json-wire-format-test.ts | 3 --- src/options.ts | 2 +- src/types.ts | 6 ++---- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/integration/use-json-wire-format/use-json-wire-format-test.ts b/integration/use-json-wire-format/use-json-wire-format-test.ts index 09fe42923..0af6ae269 100644 --- a/integration/use-json-wire-format/use-json-wire-format-test.ts +++ b/integration/use-json-wire-format/use-json-wire-format-test.ts @@ -1,8 +1,5 @@ import { Todo } from './use-json-wire-format'; -const jan1 = new Date('1970-01-01T00:00:00.000Z'); -const feb1 = new Date('1970-02-01T00:00:00.000Z'); - describe('useJsonWireFormat=true', () => { it('generates a type that compiles', () => { let t: Todo = { diff --git a/src/options.ts b/src/options.ts index 1a774b8bf..242404a74 100644 --- a/src/options.ts +++ b/src/options.ts @@ -64,7 +64,7 @@ export type Options = { useExactTypes: boolean; unknownFields: boolean; usePrototypeForDefaults: boolean; - useJsonWireFormat: boolean + useJsonWireFormat: boolean; }; export function defaultOptions(): Options { diff --git a/src/types.ts b/src/types.ts index e12e8ef51..48478da3d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -462,7 +462,7 @@ export function isEmptyType(typeName: string): boolean { } export function valueTypeName(ctx: Context, typeName: string): Code | undefined { - const useJsonWireFormat = ctx.options.onlyTypes && ctx.options.useJsonWireFormat + const useJsonWireFormat = ctx.options.onlyTypes && ctx.options.useJsonWireFormat; switch (typeName) { case '.google.protobuf.StringValue': return code`string`; @@ -478,9 +478,7 @@ export function valueTypeName(ctx: Context, typeName: string): Code | undefined case '.google.protobuf.BoolValue': return code`boolean`; case '.google.protobuf.BytesValue': - return ctx.options.env === EnvOption.NODE - ? code`Buffer` - : useJsonWireFormat ? code`string` : code`Uint8Array`; + return ctx.options.env === EnvOption.NODE ? code`Buffer` : useJsonWireFormat ? code`string` : code`Uint8Array`; case '.google.protobuf.ListValue': return code`Array`; case '.google.protobuf.Value':