ts-proto 2.10.0 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.markdown CHANGED
@@ -414,6 +414,9 @@ Generated code will be placed in the Gradle build directory.
414
414
 
415
415
  The default behavior is `keys_json`, i.e. both will be camel cased, and `json_name` will be used if set.
416
416
 
417
+ - With `--ts_proto_opt=protoJsonFormat=true` (default), the `toJSON` and `fromJSON` methods follow the [protoJSON format](https://protobuf.dev/programming-guides/json). This means `toJSON` will output standard lowerCamelCase keys (or custom `json_name` if set). `fromJSON` will accept both the lowerCamelCase key (or `json_name`) AND the original proto field name.
418
+ With `--ts_proto_opt=protoJsonFormat=false`, strict proto3 compliance is disabled. The JSON keys will simply follow the `snakeToCamel` option, and `fromJSON` will only accept that specific format.
419
+
417
420
  - With `--ts_proto_opt=outputEncodeMethods=false`, the `Message.encode` and `Message.decode` methods for working with protobuf-encoded/binary data will not be output.
418
421
 
419
422
  This is useful if you want "only types".
@@ -36,7 +36,7 @@ function generateWrapDeep(ctx, fullProtoTypeName, fieldNames) {
36
36
  const struct = createBase${(0, utils_1.wrapTypeName)(ctx.options, "Struct")}();
37
37
  ${defaultFields}
38
38
  if (object !== undefined) {
39
- for (const key of Object.keys(object)) {
39
+ for (const key of ${ctx.utils.globalThis}.Object.keys(object)) {
40
40
  ${setStatement}
41
41
  }
42
42
  }
@@ -105,7 +105,7 @@ function generateUnwrapDeep(ctx, fullProtoTypeName, fieldNames) {
105
105
  chunks.push((0, ts_poet_1.code) `unwrap(message: ${(0, utils_1.wrapTypeName)(ctx.options, "Struct")}): {[key: string]: any} {
106
106
  const object: { [key: string]: any } = {};
107
107
  if (message.fields) {
108
- for (const key of Object.keys(message.fields)) {
108
+ for (const key of ${ctx.utils.globalThis}.Object.keys(message.fields)) {
109
109
  object[key] = Value.unwrap(message.fields[key]);
110
110
  }
111
111
  }
@@ -168,7 +168,7 @@ function generateWrapShallow(ctx, fullProtoTypeName, fieldNames) {
168
168
  const struct = createBase${(0, utils_1.wrapTypeName)(ctx.options, "Struct")}();
169
169
  ${defaultFields}
170
170
  if (object !== undefined) {
171
- for (const key of Object.keys(object)) {
171
+ for (const key of ${ctx.utils.globalThis}.Object.keys(object)) {
172
172
  ${setStatement}
173
173
  }
174
174
  }
@@ -280,7 +280,7 @@ function generateUnwrapShallow(ctx, fullProtoTypeName, fieldNames) {
280
280
  chunks.push((0, ts_poet_1.code) `unwrap(message: ${(0, utils_1.wrapTypeName)(ctx.options, "Struct")}): {[key: string]: any} {
281
281
  const object: { [key: string]: any } = {};
282
282
  if (message.fields) {
283
- for (const key of Object.keys(message.fields)) {
283
+ for (const key of ${ctx.utils.globalThis}.Object.keys(message.fields)) {
284
284
  object[key] = message.fields[key];
285
285
  }
286
286
  }
package/build/src/main.js CHANGED
@@ -1358,7 +1358,8 @@ function generateEncode(ctx, fullName, messageDesc) {
1358
1358
  const isOptional = (0, types_1.isOptionalProperty)(field, messageDesc.options, options, currentFile.isProto3Syntax);
1359
1359
  if ((0, types_1.isRepeated)(field)) {
1360
1360
  if ((0, types_1.isMapType)(ctx, messageDesc, field)) {
1361
- const valueType = typeMap.get(field.typeName)[2].field[1];
1361
+ const mapInfo = (0, types_1.detectMapType)(ctx, messageDesc, field);
1362
+ const valueType = mapInfo.valueField;
1362
1363
  const maybeTypeField = (0, options_1.addTypeToMessages)(options) ? `$type: '${field.typeName.slice(1)}',` : "";
1363
1364
  const entryWriteSnippet = (0, types_1.isValueType)(ctx, valueType)
1364
1365
  ? (0, ts_poet_1.code) `
@@ -1378,7 +1379,7 @@ function generateEncode(ctx, fullName, messageDesc) {
1378
1379
  }
1379
1380
  else {
1380
1381
  chunks.push((0, ts_poet_1.code) `
1381
- Object.entries(${messageProperty}${optionalAlternative}).forEach(([key, value]) => {
1382
+ ${utils.globalThis}.Object.entries(${messageProperty}${optionalAlternative}).forEach(([key, value]: [string, ${mapInfo.valueType}]) => {
1382
1383
  ${entryWriteSnippet}
1383
1384
  });
1384
1385
  `);
@@ -1533,7 +1534,7 @@ function generateEncode(ctx, fullName, messageDesc) {
1533
1534
  });
1534
1535
  if (options.unknownFields) {
1535
1536
  chunks.push((0, ts_poet_1.code) `if (message._unknownFields !== undefined) {
1536
- for (const [key, values] of Object.entries(message._unknownFields)) {
1537
+ for (const [key, values] of ${utils.globalThis}.Object.entries(message._unknownFields)) {
1537
1538
  const tag = parseInt(key, 10);
1538
1539
  for (const value of values) {
1539
1540
  writer.uint32(tag).raw(value);
@@ -1926,7 +1927,7 @@ function generateFromJson(ctx, fullName, fullTypeName, messageDesc) {
1926
1927
  }
1927
1928
  else {
1928
1929
  const cstr = (0, case_1.capitalize)(valueType.toCodeString([]));
1929
- return (0, ts_poet_1.code) `${cstr}(${from})`;
1930
+ return (0, ts_poet_1.code) `${utils.globalThis}.${cstr}(${from})`;
1930
1931
  }
1931
1932
  }
1932
1933
  else if ((0, types_1.isObjectId)(valueField) && options.useMongoObjectId) {
@@ -1963,33 +1964,59 @@ function generateFromJson(ctx, fullName, fullTypeName, messageDesc) {
1963
1964
  };
1964
1965
  const noDefaultValue = !options.initializeFieldsAsUndefined &&
1965
1966
  (0, types_1.isOptionalProperty)(field, messageDesc.options, options, currentFile.isProto3Syntax);
1967
+ // if the protoJsonFormat option is enabled and the field name is different from the json name, we need to
1968
+ // accept both formats in the fromJSON method.
1969
+ let protoJsonComparison = (0, ts_poet_1.code) ``;
1966
1970
  // and then use the snippet to handle repeated fields if necessary
1967
1971
  if (canonicalFromJson[fullTypeName]?.[fieldName]) {
1968
1972
  chunks.push((0, ts_poet_1.code) `${fieldName}: ${canonicalFromJson[fullTypeName][fieldName]("object")},`);
1969
1973
  }
1970
1974
  else if ((0, types_1.isRepeated)(field)) {
1971
1975
  if ((0, types_1.isMapType)(ctx, messageDesc, field)) {
1976
+ const mapInfo = (0, types_1.detectMapType)(ctx, messageDesc, field);
1972
1977
  const fieldType = (0, types_1.toTypeName)(ctx, messageDesc, field);
1973
1978
  const i = convertFromObjectKey(ctx, messageDesc, field, "key");
1974
1979
  if ((0, types_1.shouldGenerateJSMapType)(ctx, messageDesc, field)) {
1975
1980
  const fallback = noDefaultValue ? (0, utils_1.nullOrUndefined)(options) : "new Map()";
1981
+ if (options.protoJsonFormat && field.name !== field.jsonName) {
1982
+ const protoJsonProperty = (0, utils_1.getPropertyAccessor)("object", field.name);
1983
+ protoJsonComparison = (0, ts_poet_1.code) `
1984
+ : ${ctx.utils.isObject}(${protoJsonProperty})
1985
+ ? (${ctx.utils.globalThis}.Object.entries(${protoJsonProperty}) as [string, any][]).reduce((acc: ${fieldType}, [key, value]: [string, any]) => {
1986
+ acc.set(${i}, ${readSnippet("value")});
1987
+ return acc;
1988
+ }, new Map())
1989
+ `;
1990
+ }
1976
1991
  chunks.push((0, ts_poet_1.code) `
1977
1992
  ${fieldKey}: ${ctx.utils.isObject}(${jsonProperty})
1978
- ? Object.entries(${jsonProperty}).reduce<${fieldType}>((acc, [key, value]) => {
1993
+ ? (${ctx.utils.globalThis}.Object.entries(${jsonProperty}) as [string, any][]).reduce((acc: ${fieldType}, [key, value]: [string, any]) => {
1979
1994
  acc.set(${i}, ${readSnippet("value")});
1980
1995
  return acc;
1981
1996
  }, new Map())
1997
+ ${protoJsonComparison}
1982
1998
  : ${fallback},
1983
1999
  `);
1984
2000
  }
1985
2001
  else {
1986
2002
  const fallback = noDefaultValue ? (0, utils_1.nullOrUndefined)(options) : "{}";
2003
+ if (options.protoJsonFormat && field.name !== field.jsonName) {
2004
+ const protoJsonProperty = (0, utils_1.getPropertyAccessor)("object", field.name);
2005
+ protoJsonComparison = (0, ts_poet_1.code) `
2006
+ : ${ctx.utils.isObject}(${protoJsonProperty})
2007
+ ? (${ctx.utils.globalThis}.Object.entries(${protoJsonProperty}) as [string, any][]).reduce((acc: ${fieldType}, [key, value]: [string, any]) => {
2008
+ acc[${i}] = ${readSnippet("value")};
2009
+ return acc;
2010
+ }, {})
2011
+ `;
2012
+ }
1987
2013
  chunks.push((0, ts_poet_1.code) `
1988
2014
  ${fieldKey}: ${ctx.utils.isObject}(${jsonProperty})
1989
- ? Object.entries(${jsonProperty}).reduce<${fieldType}>((acc, [key, value]) => {
2015
+ ? (${ctx.utils.globalThis}.Object.entries(${jsonProperty}) as [string, any][]).reduce((acc: ${fieldType}, [key, value]: [string, any]) => {
1990
2016
  acc[${i}] = ${readSnippet("value")};
1991
2017
  return acc;
1992
2018
  }, {})
2019
+ ${protoJsonComparison}
1993
2020
  : ${fallback},
1994
2021
  `);
1995
2022
  }
@@ -1998,12 +2025,22 @@ function generateFromJson(ctx, fullName, fullTypeName, messageDesc) {
1998
2025
  const fallback = noDefaultValue ? (0, utils_1.nullOrUndefined)(options) : "[]";
1999
2026
  const needMap = readSnippet("e").toString() !== (0, ts_poet_1.code) `e`.toString();
2000
2027
  if (!needMap) {
2001
- chunks.push((0, ts_poet_1.code) `${fieldKey}: ${ctx.utils.globalThis}.Array.isArray(${jsonPropertyOptional}) ? [...${jsonProperty}] : [],`);
2028
+ if (options.protoJsonFormat && field.name !== field.jsonName) {
2029
+ const protoJsonProperty = (0, utils_1.getPropertyAccessor)("object", field.name);
2030
+ const protoJsonPropertyOptional = (0, utils_1.getPropertyAccessor)("object", field.name, true);
2031
+ protoJsonComparison = (0, ts_poet_1.code) ` : ${ctx.utils.globalThis}.Array.isArray(${protoJsonPropertyOptional}) ? [...${protoJsonProperty}]`;
2032
+ }
2033
+ chunks.push((0, ts_poet_1.code) `${fieldKey}: ${ctx.utils.globalThis}.Array.isArray(${jsonPropertyOptional}) ? [...${jsonProperty}] ${protoJsonComparison} : [],`);
2002
2034
  }
2003
2035
  else {
2004
2036
  // Explicit `any` type required to make TS with noImplicitAny happy. `object` is also `any` here.
2037
+ if (options.protoJsonFormat && field.name !== field.jsonName) {
2038
+ const protoJsonProperty = (0, utils_1.getPropertyAccessor)("object", field.name);
2039
+ const protoJsonPropertyOptional = (0, utils_1.getPropertyAccessor)("object", field.name, true);
2040
+ protoJsonComparison = (0, ts_poet_1.code) ` : ${ctx.utils.globalThis}.Array.isArray(${protoJsonPropertyOptional}) ? ${protoJsonProperty}.map((e: any) => ${readSnippet("e")})`;
2041
+ }
2005
2042
  chunks.push((0, ts_poet_1.code) `
2006
- ${fieldKey}: ${ctx.utils.globalThis}.Array.isArray(${jsonPropertyOptional}) ? ${jsonProperty}.map((e: any) => ${readSnippet("e")}): ${fallback},
2043
+ ${fieldKey}: ${ctx.utils.globalThis}.Array.isArray(${jsonPropertyOptional}) ? ${jsonProperty}.map((e: any) => ${readSnippet("e")}) ${protoJsonComparison} : ${fallback},
2007
2044
  `);
2008
2045
  }
2009
2046
  }
@@ -2020,33 +2057,59 @@ function generateFromJson(ctx, fullName, fullTypeName, messageDesc) {
2020
2057
  const ternaryIf = (0, ts_poet_1.code) `${ctx.utils.isSet}(${jsonProperty})`;
2021
2058
  const ternaryThen = (0, ts_poet_1.code) `{ $case: '${fieldName}', ${valueName}: ${readSnippet(`${jsonProperty}`)}`;
2022
2059
  chunks.push((0, ts_poet_1.code) `${ternaryIf} ? ${ternaryThen}} : `);
2060
+ if (options.protoJsonFormat && field.name !== field.jsonName) {
2061
+ const protoJsonProperty = (0, utils_1.getPropertyAccessor)("object", field.name);
2062
+ const ternaryOriginalIf = (0, ts_poet_1.code) `${ctx.utils.isSet}(${protoJsonProperty})`;
2063
+ const ternaryOriginalThen = (0, ts_poet_1.code) `{ $case: '${fieldName}', ${valueName}: ${readSnippet(`${protoJsonProperty}`)}`;
2064
+ chunks.push((0, ts_poet_1.code) `${ternaryOriginalIf} ? ${ternaryOriginalThen}} : `);
2065
+ }
2023
2066
  if (field === lastCase) {
2024
2067
  chunks.push((0, ts_poet_1.code) `${(0, utils_1.nullOrUndefined)(options)},`);
2025
2068
  }
2026
2069
  }
2027
2070
  else if ((0, types_1.isAnyValueType)(field)) {
2071
+ if (options.protoJsonFormat && field.name !== field.jsonName) {
2072
+ const protoJsonProperty = (0, utils_1.getPropertyAccessor)("object", field.name);
2073
+ const protoJsonPropertyOptional = (0, utils_1.getPropertyAccessor)("object", field.name, true);
2074
+ protoJsonComparison = (0, ts_poet_1.code) ` : ${ctx.utils.isSet}(${protoJsonPropertyOptional}) ? ${readSnippet(`${protoJsonProperty}`)}`;
2075
+ }
2028
2076
  chunks.push((0, ts_poet_1.code) `${fieldKey}: ${ctx.utils.isSet}(${jsonPropertyOptional})
2029
2077
  ? ${readSnippet(`${jsonProperty}`)}
2078
+ ${protoJsonComparison}
2030
2079
  : ${(0, utils_1.nullOrUndefined)(options)},
2031
2080
  `);
2032
2081
  }
2033
2082
  else if ((0, types_1.isStructType)(field)) {
2083
+ if (options.protoJsonFormat && field.name !== field.jsonName) {
2084
+ const protoJsonProperty = (0, utils_1.getPropertyAccessor)("object", field.name);
2085
+ protoJsonComparison = (0, ts_poet_1.code) ` : ${ctx.utils.isObject}(${protoJsonProperty}) ? ${readSnippet(`${protoJsonProperty}`)}`;
2086
+ }
2034
2087
  chunks.push((0, ts_poet_1.code) `${fieldKey}: ${ctx.utils.isObject}(${jsonProperty})
2035
2088
  ? ${readSnippet(`${jsonProperty}`)}
2089
+ ${protoJsonComparison}
2036
2090
  : ${(0, utils_1.nullOrUndefined)(options)},`);
2037
2091
  }
2038
2092
  else if ((0, types_1.isListValueType)(field)) {
2093
+ if (options.protoJsonFormat && field.name !== field.jsonName) {
2094
+ const protoJsonProperty = (0, utils_1.getPropertyAccessor)("object", field.name);
2095
+ protoJsonComparison = (0, ts_poet_1.code) ` : ${ctx.utils.globalThis}.Array.isArray(${protoJsonProperty}) ? ${readSnippet(`${protoJsonProperty}`)}`;
2096
+ }
2039
2097
  chunks.push((0, ts_poet_1.code) `
2040
2098
  ${fieldKey}: ${ctx.utils.globalThis}.Array.isArray(${jsonProperty})
2041
2099
  ? ${readSnippet(`${jsonProperty}`)}
2100
+ ${protoJsonComparison}
2042
2101
  : ${(0, utils_1.nullOrUndefined)(options)},
2043
2102
  `);
2044
2103
  }
2045
2104
  else {
2046
2105
  const fallback = (0, types_1.isWithinOneOf)(field) || noDefaultValue ? (0, utils_1.nullOrUndefined)(options) : (0, types_1.defaultValue)(ctx, field);
2106
+ if (options.protoJsonFormat && field.name !== field.jsonName) {
2107
+ const protoJsonProperty = (0, utils_1.getPropertyAccessor)("object", field.name);
2108
+ protoJsonComparison = (0, ts_poet_1.code) ` : ${ctx.utils.isSet}(${protoJsonProperty}) ? ${readSnippet(`${protoJsonProperty}`)}`;
2109
+ }
2047
2110
  chunks.push((0, ts_poet_1.code) `
2048
- ${fieldKey}: ${ctx.utils.isSet}(${jsonProperty})
2049
- ? ${readSnippet(`${jsonProperty}`)}
2111
+ ${fieldKey}: ${ctx.utils.isSet}(${jsonProperty}) ? ${readSnippet(`${jsonProperty}`)}
2112
+ ${protoJsonComparison}
2050
2113
  : ${fallback},
2051
2114
  `);
2052
2115
  }
@@ -2216,9 +2279,10 @@ function generateToJson(ctx, fullName, fullProtobufTypeName, messageDesc) {
2216
2279
  `);
2217
2280
  }
2218
2281
  else {
2282
+ const mapInfo = (0, types_1.detectMapType)(ctx, messageDesc, field);
2219
2283
  chunks.push((0, ts_poet_1.code) `
2220
2284
  if (${messageProperty}) {
2221
- const entries = Object.entries(${messageProperty});
2285
+ const entries = ${utils.globalThis}.Object.entries(${messageProperty}) as [string, ${mapInfo.valueType}][];
2222
2286
  if (entries.length > 0) {
2223
2287
  ${jsonProperty} = {};
2224
2288
  entries.forEach(([k, v]) => {
@@ -2390,6 +2454,7 @@ function generateFromPartial(ctx, fullName, messageDesc) {
2390
2454
  // and then use the snippet to handle repeated fields if necessary
2391
2455
  if ((0, types_1.isRepeated)(field)) {
2392
2456
  if ((0, types_1.isMapType)(ctx, messageDesc, field)) {
2457
+ const mapInfo = (0, types_1.detectMapType)(ctx, messageDesc, field);
2393
2458
  const fieldType = (0, types_1.toTypeName)(ctx, messageDesc, field);
2394
2459
  const i = convertFromObjectKey(ctx, messageDesc, field, "key");
2395
2460
  const noValueSnippet = noDefaultValue
@@ -2410,12 +2475,15 @@ function generateFromPartial(ctx, fullName, messageDesc) {
2410
2475
  }
2411
2476
  else {
2412
2477
  chunks.push((0, ts_poet_1.code) `
2413
- ${messageProperty} = ${noValueSnippet} Object.entries(${objectProperty} ?? {}).reduce<${fieldType}>((acc, [key, value]) => {
2414
- if (value !== undefined) {
2415
- acc[${i}] = ${readSnippet("value")};
2416
- }
2417
- return acc;
2418
- }, {});
2478
+ ${messageProperty} = ${noValueSnippet} (${utils.globalThis}.Object.entries(${objectProperty} ?? {}) as [string, ${mapInfo.valueType}][]).reduce(
2479
+ (acc: ${fieldType}, [key, value]: [string, ${mapInfo.valueType}]) => {
2480
+ if (value !== undefined) {
2481
+ acc[${i}] = ${readSnippet("value")};
2482
+ }
2483
+ return acc;
2484
+ },
2485
+ {},
2486
+ );
2419
2487
  `);
2420
2488
  }
2421
2489
  }
@@ -45,6 +45,7 @@ export declare enum OutputSchemaOption {
45
45
  export type Options = {
46
46
  context: boolean;
47
47
  snakeToCamel: Array<"json" | "keys">;
48
+ protoJsonFormat: boolean;
48
49
  forceLong: LongOption;
49
50
  useJsTypeOverride: boolean;
50
51
  globalThisPolyfill: boolean;
@@ -60,6 +60,7 @@ function defaultOptions() {
60
60
  return {
61
61
  context: false,
62
62
  snakeToCamel: ["json", "keys"],
63
+ protoJsonFormat: true,
63
64
  emitDefaultValues: [],
64
65
  globalThisPolyfill: false,
65
66
  forceLong: LongOption.NUMBER,
@@ -204,6 +205,11 @@ function optionsFromParameter(parameter) {
204
205
  else if (typeof options.snakeToCamel === "string") {
205
206
  options.snakeToCamel = options.snakeToCamel.split("_");
206
207
  }
208
+ if (options.protoJsonFormat && !options.snakeToCamel.includes("json")) {
209
+ // protoJSONFormat implies snakeToCamel=json, Message field names must be mapped to lowerCamelCase and become JSON object keys.
210
+ // If the json_name field option is specified, the specified value will be used as the key instead.
211
+ options.snakeToCamel.push("json");
212
+ }
207
213
  if (options.emitDefaultValues === "json-methods") {
208
214
  options.emitDefaultValues = ["json-methods"];
209
215
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-proto",
3
- "version": "2.10.0",
3
+ "version": "2.11.0",
4
4
  "description": "",
5
5
  "main": "build/src/plugin.js",
6
6
  "repository": "github:stephenh/ts-proto",